Skip to content

Commit

Permalink
feat: Add A New Message For Storing A Code and Instantiating A Contra…
Browse files Browse the repository at this point in the history
…ct With It (#122)

* feat: add the message MsgStoreCodeAndInstantiateContract

* feat: add handler for MsgStoreCodeAndInstantiateContract

* feat: implement cli command store-instantiate

* feat: add a rest endpoint to store and instantiate

* chore: gofmt

* chore: golint

* fix: change the marshalized format of response

* fix: make StoreAndInstantiate issues two events
  • Loading branch information
loloicci authored Apr 22, 2021
1 parent 13a5efc commit 929428e
Show file tree
Hide file tree
Showing 11 changed files with 825 additions and 28 deletions.
55 changes: 28 additions & 27 deletions x/wasm/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,33 +100,34 @@ var (
)

type (
ProposalType = types.ProposalType
GenesisState = types.GenesisState
Code = types.Code
Contract = types.Contract
MsgStoreCode = types.MsgStoreCode
MsgInstantiateContract = types.MsgInstantiateContract
MsgExecuteContract = types.MsgExecuteContract
MsgMigrateContract = types.MsgMigrateContract
MsgUpdateAdmin = types.MsgUpdateAdmin
MsgClearAdmin = types.MsgClearAdmin
Model = types.Model
CodeInfo = types.CodeInfo
ContractInfo = types.ContractInfo
CreatedAt = types.AbsoluteTxPosition
Config = types.WasmConfig
MessageHandler = keeper.MessageHandler
BankEncoder = keeper.BankEncoder
CustomEncoder = keeper.CustomEncoder
StakingEncoder = keeper.StakingEncoder
WasmEncoder = keeper.WasmEncoder
MessageEncoders = keeper.MessageEncoders
Keeper = keeper.Keeper
CodeInfoResponse = types.CodeInfoResponse
ContractInfoResponse = types.ContractInfoResponse
ContractHistoryResponse = types.ContractHistoryResponse
QueryHandler = keeper.QueryHandler
QueryPlugins = keeper.QueryPlugins
ProposalType = types.ProposalType
GenesisState = types.GenesisState
Code = types.Code
Contract = types.Contract
MsgStoreCode = types.MsgStoreCode
MsgStoreCodeAndInstantiateContract = types.MsgStoreCodeAndInstantiateContract
MsgInstantiateContract = types.MsgInstantiateContract
MsgExecuteContract = types.MsgExecuteContract
MsgMigrateContract = types.MsgMigrateContract
MsgUpdateAdmin = types.MsgUpdateAdmin
MsgClearAdmin = types.MsgClearAdmin
Model = types.Model
CodeInfo = types.CodeInfo
ContractInfo = types.ContractInfo
CreatedAt = types.AbsoluteTxPosition
Config = types.WasmConfig
MessageHandler = keeper.MessageHandler
BankEncoder = keeper.BankEncoder
CustomEncoder = keeper.CustomEncoder
StakingEncoder = keeper.StakingEncoder
WasmEncoder = keeper.WasmEncoder
MessageEncoders = keeper.MessageEncoders
Keeper = keeper.Keeper
CodeInfoResponse = types.CodeInfoResponse
ContractInfoResponse = types.ContractInfoResponse
ContractHistoryResponse = types.ContractHistoryResponse
QueryHandler = keeper.QueryHandler
QueryPlugins = keeper.QueryPlugins

EncodeHandler = types.EncodeHandler
EncodeQuerier = types.EncodeQuerier
Expand Down
101 changes: 101 additions & 0 deletions x/wasm/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
txCmd.AddCommand(flags.PostCommands(
StoreCodeCmd(cdc),
InstantiateContractCmd(cdc),
StoreCodeAndInstantiateContractCmd(cdc),
ExecuteContractCmd(cdc),
MigrateContractCmd(cdc),
UpdateContractAdminCmd(cdc),
Expand Down Expand Up @@ -194,6 +195,106 @@ func parseInstantiateArgs(args []string, cliCtx context.CLIContext) (types.MsgIn
return msg, nil
}

// StoreCodeAndInstantiatecontractcmd will upload code and instantiate a contract using it
func StoreCodeAndInstantiateContractCmd(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "store-instantiate [wasm file] [json_encoded_init_args] --source [source] --builder [builder] --label [text] --admin [address,optional] --amount [coins,optional]",
Short: "Upload a wasm binary and instantiate a wasm contract from the code",
Args: cobra.ExactArgs(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)

msg, err := parseStoreCodeAndInstantiateContractArgs(args, cliCtx)
if err != nil {
return err
}
if err = msg.ValidateBasic(); err != nil {
return err
}

return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}

cmd.Flags().String(flagSource, "", "A valid URI reference to the contract's source code, optional")
cmd.Flags().String(flagBuilder, "", "A valid docker tag for the build system, optional")
cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional")
cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional")
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
}

func parseStoreCodeAndInstantiateContractArgs(args []string, cliCtx context.CLIContext) (types.MsgStoreCodeAndInstantiateContract, error) {
wasm, err := ioutil.ReadFile(args[0])
if err != nil {
return types.MsgStoreCodeAndInstantiateContract{}, err
}

// gzip the wasm file
if wasmUtils.IsWasm(wasm) {
wasm, err = wasmUtils.GzipIt(wasm)

if err != nil {
return types.MsgStoreCodeAndInstantiateContract{}, err
}
} else if !wasmUtils.IsGzip(wasm) {
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("invalid input file. Use wasm binary or gzip")
}

initMsg := args[1]

var perm *types.AccessConfig
if onlyAddrStr := viper.GetString(flagInstantiateByAddress); onlyAddrStr != "" {
allowedAddr, err := sdk.AccAddressFromBech32(onlyAddrStr)
if err != nil {
return types.MsgStoreCodeAndInstantiateContract{}, sdkerrors.Wrap(err, flagInstantiateByAddress)
}
x := types.OnlyAddress.With(allowedAddr)
perm = &x
} else if everybody := viper.GetBool(flagInstantiateByEverybody); everybody {
perm = &types.AllowEverybody
}

amounstStr := viper.GetString(flagAmount)
amount, err := sdk.ParseCoins(amounstStr)
if err != nil {
return types.MsgStoreCodeAndInstantiateContract{}, err
}

label := viper.GetString(flagLabel)
if label == "" {
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("label is required on all contracts")
}

adminStr := viper.GetString(flagAdmin)
var adminAddr sdk.AccAddress
if len(adminStr) != 0 {
adminAddr, err = sdk.AccAddressFromBech32(adminStr)
if err != nil {
return types.MsgStoreCodeAndInstantiateContract{}, sdkerrors.Wrap(err, "admin")
}
}

// build and sign the transaction, then broadcast to Tendermint
msg := types.MsgStoreCodeAndInstantiateContract{
Sender: cliCtx.GetFromAddress(),
WASMByteCode: wasm,
Source: viper.GetString(flagSource),
Builder: viper.GetString(flagBuilder),
InstantiatePermission: perm,
Label: label,
InitFunds: amount,
InitMsg: []byte(initMsg),
Admin: adminAddr,
}
return msg, nil
}

// ExecuteContractCmd will instantiate a contract from previously uploaded code.
func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Expand Down
66 changes: 66 additions & 0 deletions x/wasm/client/rest/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/wasm/code", storeCodeHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/wasm/code/{codeId}", instantiateContractHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/wasm/codeinit", storeCodeAndInstantiateContractHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/wasm/contract/{contractAddr}", executeContractHandlerFn(cliCtx)).Methods("POST")
}

Expand All @@ -36,6 +37,15 @@ type instantiateContractReq struct {
Label string `json:"label" yaml:"label"`
}

type storeCodeAndInstantiateContractReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
WasmBytes []byte `json:"wasm_bytes"`
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
Admin sdk.AccAddress `json:"admin,omitempty" yaml:"admin"`
InitMsg []byte `json:"init_msg" yaml:"init_msg"`
Label string `json:"label" yaml:"label"`
}

type executeContractReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
ExecMsg []byte `json:"exec_msg" yaml:"exec_msg"`
Expand Down Expand Up @@ -133,6 +143,62 @@ func instantiateContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
}
}

func storeCodeAndInstantiateContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req storeCodeAndInstantiateContractReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}

req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}

var err error
wasm := req.WasmBytes
if len(wasm) > maxSize {
rest.WriteErrorResponse(w, http.StatusBadRequest, "Binary size exceeds maximum limit")
return
}

// gzip the wasm file
if wasmUtils.IsWasm(wasm) {
wasm, err = wasmUtils.GzipIt(wasm)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
} else if !wasmUtils.IsGzip(wasm) {
rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid input file, use wasm binary or zip")
return
}

fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// build and sign the transaction, then broadcast to Tendermint
msg := types.MsgStoreCodeAndInstantiateContract{
Sender: fromAddr,
WASMByteCode: wasm,
InitFunds: req.Deposit,
InitMsg: req.InitMsg,
Admin: req.Admin,
Label: req.Label,
}

err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}

func executeContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req executeContractReq
Expand Down
44 changes: 44 additions & 0 deletions x/wasm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func NewHandler(k Keeper) sdk.Handler {
return handleStoreCode(ctx, k, &msg)
case MsgInstantiateContract:
return handleInstantiate(ctx, k, &msg)
case MsgStoreCodeAndInstantiateContract:
return handleStoreCodeAndInstantiate(ctx, k, &msg)
case MsgExecuteContract:
return handleExecute(ctx, k, &msg)
case MsgMigrateContract:
Expand Down Expand Up @@ -93,6 +95,48 @@ func handleInstantiate(ctx sdk.Context, k Keeper, msg *MsgInstantiateContract) (
}, nil
}

func handleStoreCodeAndInstantiate(ctx sdk.Context, k Keeper, msg *MsgStoreCodeAndInstantiateContract) (*sdk.Result, error) {
codeID, err := k.Create(ctx, msg.Sender, msg.WASMByteCode, msg.Source, msg.Builder, msg.InstantiatePermission)
if err != nil {
return nil, err
}

contractAddr, err := k.Instantiate(ctx, codeID, msg.Sender, msg.Admin, msg.InitMsg, msg.Label, msg.InitFunds)
if err != nil {
return nil, err
}

events := filterMessageEvents(ctx.EventManager())
storeEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName),
sdk.NewAttribute(types.AttributeKeySigner, msg.Sender.String()),
sdk.NewAttribute(types.AttributeKeyCodeID, fmt.Sprintf("%d", codeID)),
)
instantiateEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName),
sdk.NewAttribute(types.AttributeKeySigner, msg.Sender.String()),
sdk.NewAttribute(types.AttributeKeyCodeID, fmt.Sprintf("%d", codeID)),
sdk.NewAttribute(types.AttributeKeyContract, contractAddr.String()),
)

data := types.CodeAndContractID{
CodeID: codeID,
ContractAddress: contractAddr,
}

bz, err := types.ModuleCdc.MarshalBinaryLengthPrefixed(data)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}

return &sdk.Result{
Data: bz,
Events: append(events, storeEvent, instantiateEvent),
}, nil
}

func handleExecute(ctx sdk.Context, k Keeper, msg *MsgExecuteContract) (*sdk.Result, error) {
res, err := k.Execute(ctx, msg.Contract, msg.Sender, msg.Msg, msg.SentFunds)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions x/wasm/internal/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgStoreCode{}, "wasm/MsgStoreCode", nil)
cdc.RegisterConcrete(MsgInstantiateContract{}, "wasm/MsgInstantiateContract", nil)
cdc.RegisterConcrete(MsgStoreCodeAndInstantiateContract{}, "wasm/MsgStoreCodeAndInstantiatecontract", nil)
cdc.RegisterConcrete(MsgExecuteContract{}, "wasm/MsgExecuteContract", nil)
cdc.RegisterConcrete(MsgMigrateContract{}, "wasm/MsgMigrateContract", nil)
cdc.RegisterConcrete(MsgUpdateAdmin{}, "wasm/MsgUpdateAdmin", nil)
Expand All @@ -19,6 +20,9 @@ func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(UpdateAdminProposal{}, "wasm/UpdateAdminProposal", nil)
cdc.RegisterConcrete(ClearAdminProposal{}, "wasm/ClearAdminProposal", nil)

// tx response
cdc.RegisterConcrete(CodeAndContractID{}, "wasm/CodeAndContractID", nil)

// query responses

// For the type-tags in case of a slice item or a nested property.
Expand Down
Loading

0 comments on commit 929428e

Please sign in to comment.