diff --git a/x/wasm/client/rest/query.go b/x/wasm/client/rest/query.go new file mode 100644 index 0000000000..f64813c5e9 --- /dev/null +++ b/x/wasm/client/rest/query.go @@ -0,0 +1,133 @@ +package rest + +import ( + "encoding/json" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmwasm/wasmd/x/wasm/internal/keeper" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" + "net/http" + "strconv" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/gorilla/mux" +) + +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc("/wasm/code/", listCodesHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/wasm/code/{codeID}", queryCodeHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/wasm/contract/", listAllContractsHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/wasm/contract/{contractAddr}", queryContractHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/wasm/contract/{contractAddr}/state", queryContractStateAllHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/wasm/contract/{contractAddr}/smart", queryContractStateSmartHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/wasm/contract/{contractAddr}/raw", queryContractStateRawHandlerFn(cliCtx)).Methods("GET") +} + +func listCodesHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListCode) + res, _, err := cliCtx.Query(route) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + rest.PostProcessResponse(w, cliCtx, string(res)) + } +} + +func queryCodeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + codeID, err := strconv.ParseUint(mux.Vars(r)["codeId"], 10, 64) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + route := fmt.Sprintf("custom/%s/%s/%d", types.QuerierRoute, keeper.QueryGetCode, codeID) + res, _, err := cliCtx.Query(route) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + if len(res) == 0 { + rest.WriteErrorResponse(w, http.StatusInternalServerError, "contract not found") + return + } + var code keeper.GetCodeResponse + err = json.Unmarshal(res, &code) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + if len(code.Code) == 0 { + rest.WriteErrorResponse(w, http.StatusInternalServerError, "contract not found") + return + } + + rest.PostProcessResponse(w, cliCtx, string(code.Code)) + } +} + +func listAllContractsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListContracts) + res, _, err := cliCtx.Query(route) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + + return + } + rest.PostProcessResponse(w, cliCtx, string(res)) + } +} + +func queryContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["codeId"]) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContract, addr.String()) + res, _, err := cliCtx.Query(route) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + rest.PostProcessResponse(w, cliCtx, string(res)) + } +} + +func queryContractStateAllHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["contractAddr"]) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateAll) + res, _, err := cliCtx.Query(route) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + rest.PostProcessResponse(w, cliCtx, string(res)) + } +} + +func queryContractStateSmartHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + } +} + +func queryContractStateRawHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + } +} diff --git a/x/wasm/client/rest/rest.go b/x/wasm/client/rest/rest.go new file mode 100644 index 0000000000..4237d59e25 --- /dev/null +++ b/x/wasm/client/rest/rest.go @@ -0,0 +1,13 @@ +package rest + +import ( + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" +) + +// RegisterRoutes registers staking-related REST handlers to a router +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + registerQueryRoutes(cliCtx, r) + registerTxRoutes(cliCtx, r) +} diff --git a/x/wasm/client/rest/tx.go b/x/wasm/client/rest/tx.go new file mode 100644 index 0000000000..4f08001a80 --- /dev/null +++ b/x/wasm/client/rest/tx.go @@ -0,0 +1,166 @@ +package rest + +import ( + "net/http" + "strconv" + + "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" + + wasmUtils "github.com/cosmwasm/wasmd/x/wasm/client/utils" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" +) + +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/contract/{contractAddr}", executeContractHandlerFn(cliCtx)).Methods("POST") +} + +// limit max bytes read to prevent gzip bombs +const maxSize = 400 * 1024 + +type storeCodeReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + WasmBytes []byte `json:"wasm_bytes"` +} + +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"` +} + +type executeContractReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + ExecMsg []byte `json:"exec_msg" yaml:"exec_msg"` + Amount sdk.Coins `json:"coins" yaml:"coins"` +} + +func storeCodeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req storeCodeReq + 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.MsgStoreCode{ + Sender: fromAddr, + WASMByteCode: wasm, + } + + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} + +func instantiateContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req instantiateContractReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + vars := mux.Vars(r) + codeId := vars["codeId"] + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + // get the id of the code to instantiate + codeID, err := strconv.ParseUint(codeId, 10, 64) + if err != nil { + return + } + + msg := types.MsgInstantiateContract{ + Sender: cliCtx.GetFromAddress(), + Code: codeID, + InitFunds: req.Deposit, + InitMsg: req.InitMsg, + } + + 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 + 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 { + return + } + + msg := types.MsgExecuteContract{ + Sender: cliCtx.GetFromAddress(), + Contract: contractAddress, + Msg: req.ExecMsg, + SentFunds: req.Amount, + } + + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +}