From 4e8001b01fe0e277be98218e7c176b65149f5f03 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 16:57:10 +0100 Subject: [PATCH 01/12] Raw copy from cosmos/modules (still on v0.37) --- x/wasm/alias.go | 73 ++++ x/wasm/client/cli/tx.go | 155 ++++++++ x/wasm/genesis.go | 29 ++ x/wasm/handler.go | 99 +++++ x/wasm/internal/keeper/keeper.go | 348 +++++++++++++++++ x/wasm/internal/keeper/keeper_test.go | 176 +++++++++ x/wasm/internal/keeper/querier.go | 136 +++++++ x/wasm/internal/keeper/test_common.go | 75 ++++ x/wasm/internal/keeper/testdata/contract.wasm | Bin 0 -> 49194 bytes x/wasm/internal/types/codec.go | 23 ++ x/wasm/internal/types/errors.go | 43 +++ x/wasm/internal/types/keys.go | 48 +++ x/wasm/internal/types/msg.go | 100 +++++ x/wasm/internal/types/types.go | 80 ++++ x/wasm/module.go | 136 +++++++ x/wasm/module_test.go | 352 ++++++++++++++++++ 16 files changed, 1873 insertions(+) create mode 100644 x/wasm/alias.go create mode 100644 x/wasm/client/cli/tx.go create mode 100644 x/wasm/genesis.go create mode 100644 x/wasm/handler.go create mode 100644 x/wasm/internal/keeper/keeper.go create mode 100644 x/wasm/internal/keeper/keeper_test.go create mode 100644 x/wasm/internal/keeper/querier.go create mode 100644 x/wasm/internal/keeper/test_common.go create mode 100644 x/wasm/internal/keeper/testdata/contract.wasm create mode 100644 x/wasm/internal/types/codec.go create mode 100644 x/wasm/internal/types/errors.go create mode 100644 x/wasm/internal/types/keys.go create mode 100644 x/wasm/internal/types/msg.go create mode 100644 x/wasm/internal/types/types.go create mode 100644 x/wasm/module.go create mode 100644 x/wasm/module_test.go diff --git a/x/wasm/alias.go b/x/wasm/alias.go new file mode 100644 index 0000000000..b89d12837f --- /dev/null +++ b/x/wasm/alias.go @@ -0,0 +1,73 @@ +// nolint +package wasm + +import ( + "github.com/cosmos/modules/incubator/wasm/internal/keeper" + "github.com/cosmos/modules/incubator/wasm/internal/types" +) + +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/modules/incubator/wasm/internal/types/ +// ALIASGEN: github.com/cosmos/modules/incubator/wasm/internal/keeper/ + +const ( + DefaultCodespace = types.DefaultCodespace + CodeCreatedFailed = types.CodeCreatedFailed + CodeAccountExists = types.CodeAccountExists + CodeInstantiateFailed = types.CodeInstantiateFailed + CodeExecuteFailed = types.CodeExecuteFailed + CodeGasLimit = types.CodeGasLimit + ModuleName = types.ModuleName + StoreKey = types.StoreKey + TStoreKey = types.TStoreKey + QuerierRoute = types.QuerierRoute + RouterKey = types.RouterKey + MaxWasmSize = types.MaxWasmSize + GasMultiplier = keeper.GasMultiplier + MaxGas = keeper.MaxGas + QueryListContracts = keeper.QueryListContracts + QueryGetContract = keeper.QueryGetContract + QueryGetContractState = keeper.QueryGetContractState + QueryGetCode = keeper.QueryGetCode + QueryListCode = keeper.QueryListCode +) + +var ( + // functions aliases + RegisterCodec = types.RegisterCodec + ErrCreateFailed = types.ErrCreateFailed + ErrAccountExists = types.ErrAccountExists + ErrInstantiateFailed = types.ErrInstantiateFailed + ErrExecuteFailed = types.ErrExecuteFailed + ErrGasLimit = types.ErrGasLimit + GetCodeKey = types.GetCodeKey + GetContractAddressKey = types.GetContractAddressKey + GetContractStorePrefixKey = types.GetContractStorePrefixKey + NewCodeInfo = types.NewCodeInfo + NewParams = types.NewParams + NewWasmCoins = types.NewWasmCoins + NewContract = types.NewContract + CosmosResult = types.CosmosResult + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + MakeTestCodec = keeper.MakeTestCodec + CreateTestInput = keeper.CreateTestInput + + // variable aliases + ModuleCdc = types.ModuleCdc + KeyLastCodeID = types.KeyLastCodeID + KeyLastInstanceID = types.KeyLastInstanceID + CodeKeyPrefix = types.CodeKeyPrefix + ContractKeyPrefix = types.ContractKeyPrefix + ContractStorePrefix = types.ContractStorePrefix +) + +type ( + MsgStoreCode = types.MsgStoreCode + MsgInstantiateContract = types.MsgInstantiateContract + MsgExecuteContract = types.MsgExecuteContract + CodeInfo = types.CodeInfo + Contract = types.Contract + Keeper = keeper.Keeper +) diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go new file mode 100644 index 0000000000..29878fd7b9 --- /dev/null +++ b/x/wasm/client/cli/tx.go @@ -0,0 +1,155 @@ +package client + +import ( + "bufio" + "strconv" + "io/ioutil" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + auth "github.com/cosmos/cosmos-sdk/x/auth" + + "github.com/cosmos/modules/incubator/wasm/internal/types" +) + +const ( + flagTo = "to" + flagAmount = "amount" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd(cdc *codec.Codec) *cobra.Command { + txCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Wasm transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, + } + txCmd.AddCommand( + StoreCodeCmd(cdc), + // InstantiateContractCmd(cdc), + // ExecuteContractCmd(cdc), + ) + return txCmd +} + +// StoreCodeCmd will upload code to be reused. +func StoreCodeCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "store [from_key_or_address] [wasm file]", + Short: "Upload a wasm binary", + 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.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc) + + // parse coins trying to be sent + wasm, err := ioutil.ReadFile(args[1]) + if err != nil { + return err + } + + // build and sign the transaction, then broadcast to Tendermint + msg := types.MsgStoreCode{ + Sender: cliCtx.GetFromAddress(), + WASMByteCode: wasm, + } + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + + cmd = client.PostCommands(cmd)[0] + + return cmd +} + +// // InstantiateContractCmd will instantiate a contract from previously uploaded code. +// func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command { +// cmd := &cobra.Command{ +// Use: "create [from_key_or_address] [code_id_int64] [coins] [json_encoded_init_args]", +// Short: "Instantiate a wasm contract", +// Args: cobra.ExactArgs(4), +// RunE: func(cmd *cobra.Command, args []string) error { +// txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) +// cliCtx := context.NewCLIContextWithFrom(args[0]). +// WithCodec(cdc). +// WithAccountDecoder(cdc) + +// // get the id of the code to instantiate +// codeID, err := strconv.Atoi(args[1]) +// if err != nil { +// return err +// } + +// // parse coins trying to be sent +// coins, err := sdk.ParseCoins(args[2]) +// if err != nil { +// return err +// } + +// initMsg := args[3] + +// // build and sign the transaction, then broadcast to Tendermint +// msg := MsgCreateContract{ +// Sender: cliCtx.GetFromAddress(), +// Code: CodeID(codeID), +// InitFunds: coins, +// InitMsg: []byte(initMsg), +// } +// return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) +// }, +// } + +// cmd = client.PostCommands(cmd)[0] + +// return cmd +// } + +// // ExecuteContractCmd will instantiate a contract from previously uploaded code. +// func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command { +// cmd := &cobra.Command{ +// Use: "send [from_key_or_address] [contract_addr_bech32] [coins] [json_encoded_send_args]", +// Short: "Instantiate a wasm contract", +// Args: cobra.ExactArgs(4), +// RunE: func(cmd *cobra.Command, args []string) error { +// txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) +// cliCtx := context.NewCLIContextWithFrom(args[0]). +// WithCodec(cdc). +// WithAccountDecoder(cdc) + +// // get the id of the code to instantiate +// contractAddr, err := sdk.AccAddressFromBech32(args[1]) +// if err != nil { +// return err +// } + +// // parse coins trying to be sent +// coins, err := sdk.ParseCoins(args[2]) +// if err != nil { +// return err +// } + +// sendMsg := args[3] + +// // build and sign the transaction, then broadcast to Tendermint +// msg := MsgSendContract{ +// Sender: cliCtx.GetFromAddress(), +// Contract: contractAddr, +// Payment: coins, +// Msg: []byte(sendMsg), +// } +// return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) +// }, +// } + +// cmd = client.PostCommands(cmd)[0] + +// return cmd +// } diff --git a/x/wasm/genesis.go b/x/wasm/genesis.go new file mode 100644 index 0000000000..1d6e31d999 --- /dev/null +++ b/x/wasm/genesis.go @@ -0,0 +1,29 @@ +package wasm + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + // authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + // "github.com/cosmos/modules/incubator/wasm/internal/types" +) + +type GenesisState struct { + // TODO +} + +// InitGenesis sets supply information for genesis. +// +// CONTRACT: all types of accounts must have been already initialized/created +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + // TODO +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + return GenesisState{} +} + +// ValidateGenesis performs basic validation of supply genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { + return nil +} diff --git a/x/wasm/handler.go b/x/wasm/handler.go new file mode 100644 index 0000000000..a01ca8e7d7 --- /dev/null +++ b/x/wasm/handler.go @@ -0,0 +1,99 @@ +package wasm + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + AttributeKeyContract = "contract_address" + AttributeKeyCodeID = "code_id" +) + +// NewHandler returns a handler for "bank" type messages. +func NewHandler(k Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case MsgStoreCode: + return handleStoreCode(ctx, k, msg) + + case MsgInstantiateContract: + return handleInstantiate(ctx, k, msg) + + case MsgExecuteContract: + return handleExecute(ctx, k, msg) + + default: + errMsg := fmt.Sprintf("unrecognized wasm message type: %T", msg) + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func handleStoreCode(ctx sdk.Context, k Keeper, msg MsgStoreCode) sdk.Result { + codeID, err := k.Create(ctx, msg.Sender, msg.WASMByteCode) + if err != nil { + return err.Result() + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, "store-code"), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), + sdk.NewAttribute(AttributeKeyCodeID, fmt.Sprintf("%d", codeID)), + ), + ) + + return sdk.Result{ + Data: []byte(fmt.Sprintf("%d", codeID)), + Events: ctx.EventManager().Events(), + } +} + +func handleInstantiate(ctx sdk.Context, k Keeper, msg MsgInstantiateContract) sdk.Result { + contractAddr, err := k.Instantiate(ctx, msg.Sender, msg.Code, msg.InitMsg, msg.InitFunds) + if err != nil { + return err.Result() + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, "instantiate"), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), + sdk.NewAttribute(AttributeKeyCodeID, fmt.Sprintf("%d", msg.Code)), + sdk.NewAttribute(AttributeKeyContract, contractAddr.String()), + ), + ) + + return sdk.Result{ + Data: contractAddr, + Events: ctx.EventManager().Events(), + } +} + +func handleExecute(ctx sdk.Context, k Keeper, msg MsgExecuteContract) sdk.Result { + res, err := k.Execute(ctx, msg.Contract, msg.Sender, msg.SentFunds, msg.Msg) + if err != nil { + return err.Result() + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(sdk.AttributeKeyAction, "execute"), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), + sdk.NewAttribute(AttributeKeyContract, msg.Contract.String()), + ), + ) + + res.Events = append(res.Events, ctx.EventManager().Events()...) + return res +} diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go new file mode 100644 index 0000000000..882b13e15a --- /dev/null +++ b/x/wasm/internal/keeper/keeper.go @@ -0,0 +1,348 @@ +package keeper + +import ( + "encoding/binary" + "fmt" + "path/filepath" + + wasm "github.com/confio/go-cosmwasm" + wasmTypes "github.com/confio/go-cosmwasm/types" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/modules/incubator/wasm/internal/types" +) + +// GasMultiplier is how many cosmwasm gas points = 1 sdk gas point +// SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 +// A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io +// Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read) +const GasMultiplier = 100 + +// MaxGas for a contract is 900 million (enforced in rust) +const MaxGas = 900_000_000 + +// Keeper will have a reference to Wasmer with it's own data directory. +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + accountKeeper auth.AccountKeeper + bankKeeper bank.Keeper + + router sdk.Router + + wasmer wasm.Wasmer +} + +// NewKeeper creates a new contract Keeper instance +func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, accountKeeper auth.AccountKeeper, bankKeeper bank.Keeper, router sdk.Router, homeDir string) Keeper { + wasmer, err := wasm.NewWasmer(filepath.Join(homeDir, "wasm"), 3) + if err != nil { + panic(err) + } + + return Keeper{ + storeKey: storeKey, + cdc: cdc, + wasmer: *wasmer, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + router: router, + } +} + +// Create uploads and compiles a WASM contract, returning a short identifier for the contract +func (k Keeper) Create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte) (codeID uint64, sdkErr sdk.Error) { + codeHash, err := k.wasmer.Create(wasmCode) + if err != nil { + return 0, types.ErrCreateFailed(err) + } + + store := ctx.KVStore(k.storeKey) + codeID = k.autoIncrementID(ctx, types.KeyLastCodeID) + contractInfo := types.NewCodeInfo(codeHash, creator) + // 0x01 | codeID (uint64) -> ContractInfo + store.Set(types.GetCodeKey(codeID), k.cdc.MustMarshalBinaryBare(contractInfo)) + + return codeID, nil +} + +// Instantiate creates an instance of a WASM contract +func (k Keeper) Instantiate(ctx sdk.Context, creator sdk.AccAddress, codeID uint64, initMsg []byte, deposit sdk.Coins) (sdk.AccAddress, sdk.Error) { + // create contract address + contractAddress := k.generateContractAddress(ctx, codeID) + existingAccnt := k.accountKeeper.GetAccount(ctx, contractAddress) + if existingAccnt != nil { + return nil, types.ErrAccountExists(existingAccnt.GetAddress()) + } + + // deposit initial contract funds + sdkerr := k.bankKeeper.SendCoins(ctx, creator, contractAddress, deposit) + if sdkerr != nil { + return nil, sdkerr + } + contractAccount := k.accountKeeper.GetAccount(ctx, contractAddress) + + // get contact info + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.GetCodeKey(codeID)) + var codeInfo types.CodeInfo + if bz != nil { + k.cdc.MustUnmarshalBinaryBare(bz, &codeInfo) + } + + // prepare params for contract instantiate call + params := types.NewParams(ctx, creator, deposit, contractAccount) + + // create prefixed data store + // 0x03 | contractAddress (sdk.AccAddress) + prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) + + // instantiate wasm contract + gas := gasForContract(ctx) + res, err := k.wasmer.Instantiate(codeInfo.CodeHash, params, initMsg, prefixStore, gas) + if err != nil { + return contractAddress, types.ErrInstantiateFailed(err) + } + consumeGas(ctx, res.GasUsed) + + sdkerr = k.dispatchMessages(ctx, contractAccount, res.Messages) + if sdkerr != nil { + return nil, sdkerr + } + + // persist instance + instance := types.NewContract(codeID, creator, initMsg, prefixStore) + // 0x02 | contractAddress (sdk.AccAddress) -> Instance + store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(instance)) + + return contractAddress, nil +} + +// Execute executes the contract instance +func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, coins sdk.Coins, msgs []byte) (sdk.Result, sdk.Error) { + store := ctx.KVStore(k.storeKey) + + var contract types.Contract + contractBz := store.Get(types.GetContractAddressKey(contractAddress)) + if contractBz != nil { + k.cdc.MustUnmarshalBinaryBare(contractBz, &contract) + } + + var codeInfo types.CodeInfo + contractInfoBz := store.Get(types.GetCodeKey(contract.CodeID)) + if contractInfoBz != nil { + k.cdc.MustUnmarshalBinaryBare(contractInfoBz, &codeInfo) + } + + // add more funds + sdkerr := k.bankKeeper.SendCoins(ctx, caller, contractAddress, coins) + if sdkerr != nil { + return sdk.Result{}, sdkerr + } + contractAccount := k.accountKeeper.GetAccount(ctx, contractAddress) + params := types.NewParams(ctx, caller, coins, contractAccount) + + prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) + + gas := gasForContract(ctx) + res, err := k.wasmer.Execute(codeInfo.CodeHash, params, msgs, prefixStore, gas) + if err != nil { + return sdk.Result{}, types.ErrExecuteFailed(err) + } + consumeGas(ctx, res.GasUsed) + + sdkerr = k.dispatchMessages(ctx, contractAccount, res.Messages) + if sdkerr != nil { + return sdk.Result{}, sdkerr + } + + return types.CosmosResult(*res), nil +} + +func (k Keeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.Contract { + store := ctx.KVStore(k.storeKey) + var contract types.Contract + contractBz := store.Get(types.GetContractAddressKey(contractAddress)) + if contractBz == nil { + return nil + } + k.cdc.MustUnmarshalBinaryBare(contractBz, &contract) + return &contract +} + +func (k Keeper) ListContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.Contract) bool) { + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.ContractKeyPrefix) + iter := prefixStore.Iterator(nil, nil) + for ; iter.Valid(); iter.Next() { + var contract types.Contract + k.cdc.MustUnmarshalBinaryBare(iter.Value(), &contract) + // cb returns true to stop early + if cb(iter.Key(), contract) { + break + } + } +} + +func (k Keeper) GetContractState(ctx sdk.Context, contractAddress sdk.AccAddress) sdk.Iterator { + prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) + return prefixStore.Iterator(nil, nil) +} + +func (k Keeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo { + store := ctx.KVStore(k.storeKey) + var codeInfo types.CodeInfo + codeInfoBz := store.Get(types.GetCodeKey(codeID)) + if codeInfoBz == nil { + return nil + } + k.cdc.MustUnmarshalBinaryBare(codeInfoBz, &codeInfo) + return &codeInfo +} + +func (k Keeper) GetByteCode(ctx sdk.Context, codeID uint64) ([]byte, error) { + store := ctx.KVStore(k.storeKey) + var codeInfo types.CodeInfo + codeInfoBz := store.Get(types.GetCodeKey(codeID)) + if codeInfoBz == nil { + return nil, nil + } + k.cdc.MustUnmarshalBinaryBare(codeInfoBz, &codeInfo) + return k.wasmer.GetCode(codeInfo.CodeHash) +} + +func (k Keeper) dispatchMessages(ctx sdk.Context, contract exported.Account, msgs []wasmTypes.CosmosMsg) sdk.Error { + for _, msg := range msgs { + if err := k.dispatchMessage(ctx, contract, msg); err != nil { + return err + } + } + return nil +} + +func (k Keeper) dispatchMessage(ctx sdk.Context, contract exported.Account, msg wasmTypes.CosmosMsg) sdk.Error { + // we check each type (pointers would make it easier to test if set) + if msg.Send.FromAddress != "" { + sendMsg, err := convertCosmosSendMsg(msg.Send) + if err != nil { + return err + } + return k.handleSdkMessage(ctx, contract, sendMsg) + } else if msg.Contract.ContractAddr != "" { + targetAddr, stderr := sdk.AccAddressFromBech32(msg.Contract.ContractAddr) + if stderr != nil { + return sdk.ErrInvalidAddress(msg.Contract.ContractAddr) + } + // TODO: use non nil payment once we update go-cosmwasm (ContractMsg contains optional payment) + _, err := k.Execute(ctx, targetAddr, contract.GetAddress(), nil, []byte(msg.Contract.Msg)) + if err != nil { + return err + } + } else if msg.Opaque.Data != "" { + // TODO: handle opaque + panic("dispatch opaque message not yet implemented") + } + // what is it? + panic(fmt.Sprintf("Unknown CosmosMsg: %#v", msg)) +} + +func convertCosmosSendMsg(msg wasmTypes.SendMsg) (bank.MsgSend, sdk.Error) { + fromAddr, stderr := sdk.AccAddressFromBech32(msg.FromAddress) + if stderr != nil { + return bank.MsgSend{}, sdk.ErrInvalidAddress(msg.FromAddress) + } + toAddr, stderr := sdk.AccAddressFromBech32(msg.ToAddress) + if stderr != nil { + return bank.MsgSend{}, sdk.ErrInvalidAddress(msg.ToAddress) + } + + var coins sdk.Coins + for _, coin := range msg.Amount { + amount, ok := sdk.NewIntFromString(coin.Amount) + if !ok { + return bank.MsgSend{}, sdk.ErrInvalidCoins(coin.Amount + coin.Denom) + } + c := sdk.Coin{ + Denom: coin.Denom, + Amount: amount, + } + coins = append(coins, c) + } + sendMsg := bank.MsgSend{ + FromAddress: fromAddr, + ToAddress: toAddr, + Amount: coins, + } + return sendMsg, nil +} + +func (k Keeper) handleSdkMessage(ctx sdk.Context, contract exported.Account, msg sdk.Msg) sdk.Error { + // make sure this account can send it + contractAddr := contract.GetAddress() + for _, acct := range msg.GetSigners() { + if !acct.Equals(contractAddr) { + return sdk.ErrUnauthorized("contract doesn't have permission") + } + } + + // find the handler and execute it + h := k.router.Route(msg.Route()) + if h == nil { + return sdk.ErrUnknownRequest(msg.Route()) + } + res := h(ctx, msg) + if !res.IsOK() { + return sdk.NewError(res.Codespace, res.Code, res.Log) + } + return nil +} + +func gasForContract(ctx sdk.Context) uint64 { + meter := ctx.GasMeter() + remaining := (meter.Limit() - meter.GasConsumed()) * GasMultiplier + if remaining > MaxGas { + return MaxGas + } + return remaining +} + +func consumeGas(ctx sdk.Context, gas uint64) { + consumed := gas / GasMultiplier + ctx.GasMeter().ConsumeGas(consumed, "wasm contract") +} + +// generates a contract address from codeID + instanceID +func (k Keeper) generateContractAddress(ctx sdk.Context, codeID uint64) sdk.AccAddress { + instanceID := k.autoIncrementID(ctx, types.KeyLastInstanceID) + // NOTE: It is possible to get a duplicate address if either codeID or instanceID + // overflow 32 bits. This is highly improbable, but something that could be refactored. + contractID := codeID<<32 + instanceID + return addrFromUint64(contractID) +} + +func (k Keeper) autoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 { + store := ctx.KVStore(k.storeKey) + bz := store.Get(lastIDKey) + id := uint64(1) + if bz != nil { + id = binary.BigEndian.Uint64(bz) + } + bz = sdk.Uint64ToBigEndian(id + 1) + store.Set(lastIDKey, bz) + return id +} + +func addrFromUint64(id uint64) sdk.AccAddress { + addr := make([]byte, 20) + addr[0] = 'C' + binary.PutUvarint(addr[1:], id) + return sdk.AccAddress(crypto.AddressHash(addr)) +} diff --git a/x/wasm/internal/keeper/keeper_test.go b/x/wasm/internal/keeper/keeper_test.go new file mode 100644 index 0000000000..8dbbc458eb --- /dev/null +++ b/x/wasm/internal/keeper/keeper_test.go @@ -0,0 +1,176 @@ +package keeper + +import ( + "encoding/json" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +func TestNewKeeper(t *testing.T) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + _, _, keeper := CreateTestInput(t, false, tempDir) + require.NotNil(t, keeper) +} + +func TestCreate(t *testing.T) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir) + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit) + + wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") + require.NoError(t, err) + + contractID, err := keeper.Create(ctx, creator, wasmCode) + require.NoError(t, err) + require.Equal(t, uint64(1), contractID) +} + +func TestInstantiate(t *testing.T) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir) + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit) + + wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") + require.NoError(t, err) + + contractID, err := keeper.Create(ctx, creator, wasmCode) + require.NoError(t, err) + + initMsg := InitMsg{ + Verifier: "fred", + Beneficiary: "bob", + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + + gasBefore := ctx.GasMeter().GasConsumed() + + // create with no balance is also legal + addr, err := keeper.Instantiate(ctx, creator, contractID, initMsgBz, nil) + require.NoError(t, err) + require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String()) + + gasAfter := ctx.GasMeter().GasConsumed() + kvStoreGas := uint64(28757) // calculated by disabling contract gas reduction and running test + require.Equal(t, kvStoreGas+285, gasAfter-gasBefore) +} + +func TestExecute(t *testing.T) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir) + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit)) + fred := createFakeFundedAccount(ctx, accKeeper, topUp) + + wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") + require.NoError(t, err) + + contractID, err := keeper.Create(ctx, creator, wasmCode) + require.NoError(t, err) + + _, _, bob := keyPubAddr() + initMsg := InitMsg{ + Verifier: fred.String(), + Beneficiary: bob.String(), + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + + addr, err := keeper.Instantiate(ctx, creator, contractID, initMsgBz, deposit) + require.NoError(t, err) + require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String()) + + // ensure bob doesn't exist + bobAcct := accKeeper.GetAccount(ctx, bob) + require.Nil(t, bobAcct) + + // ensure funder has reduced balance + creatorAcct := accKeeper.GetAccount(ctx, creator) + require.NotNil(t, creatorAcct) + // we started at 2*deposit, should have spent one above + assert.Equal(t, deposit, creatorAcct.GetCoins()) + + // ensure contract has updated balance + contractAcct := accKeeper.GetAccount(ctx, addr) + require.NotNil(t, contractAcct) + assert.Equal(t, deposit, contractAcct.GetCoins()) + + // unauthorized - trialCtx so we don't change state + trialCtx := ctx.WithMultiStore(ctx.MultiStore().CacheWrap().(sdk.MultiStore)) + res, err := keeper.Execute(trialCtx, addr, creator, nil, []byte(`{}`)) + require.Error(t, err) + require.Contains(t, err.Error(), "Unauthorized") + + // verifier can execute, and get proper gas amount + start := time.Now() + gasBefore := ctx.GasMeter().GasConsumed() + + res, err = keeper.Execute(ctx, addr, fred, topUp, []byte(`{}`)) + diff := time.Now().Sub(start) + require.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, uint64(81891), res.GasUsed) + + // make sure gas is properly deducted from ctx + gasAfter := ctx.GasMeter().GasConsumed() + kvStoreGas := uint64(30321) // calculated by disabling contract gas reduction and running test + require.Equal(t, kvStoreGas+815, gasAfter-gasBefore) + + // ensure bob now exists and got both payments released + bobAcct = accKeeper.GetAccount(ctx, bob) + require.NotNil(t, bobAcct) + balance := bobAcct.GetCoins() + assert.Equal(t, deposit.Add(topUp), balance) + + // ensure contract has updated balance + contractAcct = accKeeper.GetAccount(ctx, addr) + require.NotNil(t, contractAcct) + assert.Equal(t, sdk.Coins(nil), contractAcct.GetCoins()) + + t.Logf("Duration: %v (81488 gas)\n", diff) +} + +type InitMsg struct { + Verifier string `json:"verifier"` + Beneficiary string `json:"beneficiary"` +} + +func createFakeFundedAccount(ctx sdk.Context, am auth.AccountKeeper, coins sdk.Coins) sdk.AccAddress { + _, _, addr := keyPubAddr() + baseAcct := auth.NewBaseAccountWithAddress(addr) + _ = baseAcct.SetCoins(coins) + am.SetAccount(ctx, &baseAcct) + + return addr +} + +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { + key := ed25519.GenPrivKey() + pub := key.PubKey() + addr := sdk.AccAddress(pub.Address()) + return key, pub, addr +} diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go new file mode 100644 index 0000000000..e96969eaf1 --- /dev/null +++ b/x/wasm/internal/keeper/querier.go @@ -0,0 +1,136 @@ +package keeper + +import ( + "encoding/json" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/modules/incubator/wasm/internal/types" +) + +const ( + QueryListContracts = "list-contracts" + QueryGetContract = "contract-info" + QueryGetContractState = "contract-state" + QueryGetCode = "code" + QueryListCode = "list-code" +) + +// NewQuerier creates a new querier +func NewQuerier(keeper Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { + switch path[0] { + case QueryGetContract: + return queryContractInfo(ctx, path[1], req, keeper) + case QueryListContracts: + return queryContractList(ctx, req, keeper) + case QueryGetContractState: + return queryContractState(ctx, path[1], req, keeper) + case QueryGetCode: + return queryCode(ctx, path[1], req, keeper) + case QueryListCode: + return queryCodeList(ctx, req, keeper) + default: + return nil, sdk.ErrUnknownRequest("unknown data query endpoint") + } + } +} + +func queryContractInfo(ctx sdk.Context, bech string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + addr, err := sdk.AccAddressFromBech32(bech) + if err != nil { + return nil, sdk.ErrUnknownRequest(err.Error()) + } + info := keeper.GetContractInfo(ctx, addr) + + bz, err := json.MarshalIndent(info, "", " ") + if err != nil { + return nil, sdk.ErrInvalidAddress(err.Error()) + } + return bz, nil +} + +func queryContractList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + var addrs []string + keeper.ListContractInfo(ctx, func(addr sdk.AccAddress, _ types.Contract) bool { + addrs = append(addrs, addr.String()) + return false + }) + bz, err := json.MarshalIndent(addrs, "", " ") + if err != nil { + return nil, sdk.ErrInvalidAddress(err.Error()) + } + return bz, nil +} + +type model struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func queryContractState(ctx sdk.Context, bech string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + addr, err := sdk.AccAddressFromBech32(bech) + if err != nil { + return nil, sdk.ErrUnknownRequest(err.Error()) + } + iter := keeper.GetContractState(ctx, addr) + + var state []model + for ; iter.Valid(); iter.Next() { + m := model{ + Key: string(iter.Key()), + Value: string(iter.Value()), + } + state = append(state, m) + } + + bz, err := json.MarshalIndent(state, "", " ") + if err != nil { + return nil, sdk.ErrUnknownRequest(err.Error()) + } + return bz, nil +} + +type wasmCode struct { + Code []byte `json:"code", yaml:"code"` +} + +func queryCode(ctx sdk.Context, codeIDstr string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + codeID, err := strconv.ParseUint(codeIDstr, 10, 64) + if err != nil { + return nil, sdk.ErrUnknownRequest("invalid codeID: " + err.Error()) + } + + code, err := keeper.GetByteCode(ctx, codeID) + if err != nil { + return nil, sdk.ErrUnknownRequest("loading wasm code: " + err.Error()) + } + + bz, err := json.MarshalIndent(wasmCode{code}, "", " ") + if err != nil { + return nil, sdk.ErrUnknownRequest(err.Error()) + } + return bz, nil +} + +func queryCodeList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + var info []*types.CodeInfo + + i := uint64(1) + for true { + res := keeper.GetCodeInfo(ctx, i) + i++ + if res == nil { + break + } + info = append(info, res) + } + + bz, err := json.MarshalIndent(info, "", " ") + if err != nil { + return nil, sdk.ErrUnknownRequest(err.Error()) + } + return bz, nil +} diff --git a/x/wasm/internal/keeper/test_common.go b/x/wasm/internal/keeper/test_common.go new file mode 100644 index 0000000000..40242c91c2 --- /dev/null +++ b/x/wasm/internal/keeper/test_common.go @@ -0,0 +1,75 @@ +package keeper + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" +) + +func MakeTestCodec() *codec.Codec { + var cdc = codec.New() + + // Register AppAccount + cdc.RegisterInterface((*authexported.Account)(nil), nil) + cdc.RegisterConcrete(&auth.BaseAccount{}, "test/wasm/BaseAccount", nil) + codec.RegisterCrypto(cdc) + + return cdc +} + +func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string) (sdk.Context, auth.AccountKeeper, Keeper) { + keyContract := sdk.NewKVStoreKey(types.StoreKey) + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keyContract, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + err := ms.LoadLatestVersion() + require.Nil(t, err) + + ctx := sdk.NewContext(ms, abci.Header{}, isCheckTx, log.NewNopLogger()) + cdc := MakeTestCodec() + + pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) + + accountKeeper := auth.NewAccountKeeper( + cdc, // amino codec + keyAcc, // target store + pk.Subspace(auth.DefaultParamspace), + auth.ProtoBaseAccount, // prototype + ) + + bk := bank.NewBaseKeeper( + accountKeeper, + pk.Subspace(bank.DefaultParamspace), + bank.DefaultCodespace, + nil, + ) + bk.SetSendEnabled(ctx, true) + + // TODO: register more than bank.send + router := baseapp.NewRouter() + h := bank.NewHandler(bk) + router.AddRoute(bank.RouterKey, h) + + keeper := NewKeeper(cdc, keyContract, accountKeeper, bk, router, tempDir) + + return ctx, accountKeeper, keeper +} diff --git a/x/wasm/internal/keeper/testdata/contract.wasm b/x/wasm/internal/keeper/testdata/contract.wasm new file mode 100644 index 0000000000000000000000000000000000000000..5af085eb19fddc762e35c58af76c9556a8ed0c23 GIT binary patch literal 49194 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 literal 0 HcmV?d00001 diff --git a/x/wasm/internal/types/codec.go b/x/wasm/internal/types/codec.go new file mode 100644 index 0000000000..7a66883b22 --- /dev/null +++ b/x/wasm/internal/types/codec.go @@ -0,0 +1,23 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + // "github.com/cosmos/cosmos-sdk/x/supply/exported" +) + +// RegisterCodec registers the account types and interface +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(&MsgStoreCode{}, "wasm/store-code", nil) + cdc.RegisterConcrete(&MsgInstantiateContract{}, "wasm/instantiate", nil) + cdc.RegisterConcrete(&MsgExecuteContract{}, "wasm/execute", nil) +} + +// ModuleCdc generic sealed codec to be used throughout module +var ModuleCdc *codec.Codec + +func init() { + cdc := codec.New() + RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + ModuleCdc = cdc.Seal() +} diff --git a/x/wasm/internal/types/errors.go b/x/wasm/internal/types/errors.go new file mode 100644 index 0000000000..0b79f2b52d --- /dev/null +++ b/x/wasm/internal/types/errors.go @@ -0,0 +1,43 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Codes for wasm contract errors +const ( + DefaultCodespace sdk.CodespaceType = ModuleName + + CodeCreatedFailed sdk.CodeType = 1 + CodeAccountExists sdk.CodeType = 2 + CodeInstantiateFailed sdk.CodeType = 3 + CodeExecuteFailed sdk.CodeType = 4 + CodeGasLimit sdk.CodeType = 5 +) + +// ErrCreateFailed error for wasm code that has already been uploaded or failed +func ErrCreateFailed(err error) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeCreatedFailed, fmt.Sprintf("create wasm contract failed: %s", err.Error())) +} + +// ErrAccountExists error for a contract account that already exists +func ErrAccountExists(addr sdk.AccAddress) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeAccountExists, fmt.Sprintf("contract account %s already exists", addr.String())) +} + +// ErrInstantiateFailed error for rust instantiate contract failure +func ErrInstantiateFailed(err error) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInstantiateFailed, fmt.Sprintf("instantiate wasm contract failed: %s", err.Error())) +} + +// ErrExecuteFailed error for rust execution contract failure +func ErrExecuteFailed(err error) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeExecuteFailed, fmt.Sprintf("execute wasm contract failed: %s", err.Error())) +} + +// ErrGasLimit error for out of gas +func ErrGasLimit(msg string) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeGasLimit, fmt.Sprintf("insufficient gas: %s", msg)) +} diff --git a/x/wasm/internal/types/keys.go b/x/wasm/internal/types/keys.go new file mode 100644 index 0000000000..c7c628dd13 --- /dev/null +++ b/x/wasm/internal/types/keys.go @@ -0,0 +1,48 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // ModuleName is the name of the contract module + ModuleName = "wasm" + + // StoreKey is the string store representation + StoreKey = ModuleName + + // TStoreKey is the string transient store representation + TStoreKey = "transient_" + ModuleName + + // QuerierRoute is the querier route for the staking module + QuerierRoute = ModuleName + + // RouterKey is the msg router key for the staking module + RouterKey = ModuleName +) + +// nolint +var ( + KeyLastCodeID = []byte("lastCodeId") + KeyLastInstanceID = []byte("lastContractId") + + CodeKeyPrefix = []byte{0x01} + ContractKeyPrefix = []byte{0x02} + ContractStorePrefix = []byte{0x03} +) + +// GetCodeKey constructs the key for retreiving the ID for the WASM code +func GetCodeKey(contractID uint64) []byte { + contractIDBz := sdk.Uint64ToBigEndian(contractID) + return append(CodeKeyPrefix, contractIDBz...) +} + +// GetContractAddressKey returns the key for the WASM contract instance +func GetContractAddressKey(addr sdk.AccAddress) []byte { + return append(ContractKeyPrefix, addr...) +} + +// GetContractStorePrefixKey returns the store prefix for the WASM contract instance +func GetContractStorePrefixKey(addr sdk.AccAddress) []byte { + return append(ContractStorePrefix, addr...) +} diff --git a/x/wasm/internal/types/msg.go b/x/wasm/internal/types/msg.go new file mode 100644 index 0000000000..2859765f03 --- /dev/null +++ b/x/wasm/internal/types/msg.go @@ -0,0 +1,100 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + MaxWasmSize = 500 * 1024 +) + +type MsgStoreCode struct { + Sender sdk.AccAddress `json:"sender" yaml:"sender"` + WASMByteCode []byte `json:"wasm_byte_code" yaml:"wasm_byte_code"` +} + +func (msg MsgStoreCode) Route() string { + return RouterKey +} + +func (msg MsgStoreCode) Type() string { + return "store-code" +} + +func (msg MsgStoreCode) ValidateBasic() sdk.Error { + if len(msg.WASMByteCode) == 0 { + return sdk.ErrInternal("empty wasm code") + } + if len(msg.WASMByteCode) > MaxWasmSize { + return sdk.ErrInternal("wasm code too large") + } + return nil +} + +func (msg MsgStoreCode) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +func (msg MsgStoreCode) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} + +type MsgInstantiateContract struct { + Sender sdk.AccAddress `json:"sender" yaml:"sender"` + Code uint64 `json:"code_id" yaml:"code_id"` + InitMsg []byte `json:"init_msg" yaml:"init_msg"` + InitFunds sdk.Coins `json:"init_funds" yaml:"init_funds"` +} + +func (msg MsgInstantiateContract) Route() string { + return RouterKey +} + +func (msg MsgInstantiateContract) Type() string { + return "instantiate" +} + +func (msg MsgInstantiateContract) ValidateBasic() sdk.Error { + if msg.InitFunds.IsAnyNegative() { + return sdk.ErrInvalidCoins("negative InitFunds") + } + return nil +} + +func (msg MsgInstantiateContract) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +func (msg MsgInstantiateContract) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} + +type MsgExecuteContract struct { + Sender sdk.AccAddress `json:"sender" yaml:"sender"` + Contract sdk.AccAddress `json:"contract" yaml:"contract"` + Msg []byte `json:"msg" yaml:"msg"` + SentFunds sdk.Coins `json:"sent_funds" yaml:"sent_funds"` +} + +func (msg MsgExecuteContract) Route() string { + return RouterKey +} + +func (msg MsgExecuteContract) Type() string { + return "execute" +} + +func (msg MsgExecuteContract) ValidateBasic() sdk.Error { + if msg.SentFunds.IsAnyNegative() { + return sdk.ErrInvalidCoins("negative SentFunds") + } + return nil +} + +func (msg MsgExecuteContract) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +func (msg MsgExecuteContract) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} diff --git a/x/wasm/internal/types/types.go b/x/wasm/internal/types/types.go new file mode 100644 index 0000000000..43283d64f8 --- /dev/null +++ b/x/wasm/internal/types/types.go @@ -0,0 +1,80 @@ +package types + +import ( + wasmTypes "github.com/confio/go-cosmwasm/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +// CodeInfo is data for the uploaded contract WASM code +type CodeInfo struct { + CodeHash []byte `json:"code_hash"` + Creator sdk.AccAddress `json:"creator"` +} + +// NewCodeInfo fills a new Contract struct +func NewCodeInfo(codeHash []byte, creator sdk.AccAddress) CodeInfo { + return CodeInfo{ + CodeHash: codeHash, + Creator: creator, + } +} + +// Contract stores a WASM contract instance +type Contract struct { + CodeID uint64 `json:"code_id"` + Creator sdk.AccAddress `json:"creator"` + InitMsg []byte `json:"init_msg"` + PrefixStore prefix.Store `json:"prefix_store"` +} + +// NewParams initializes params for a contract instance +func NewParams(ctx sdk.Context, creator sdk.AccAddress, deposit sdk.Coins, contractAcct auth.Account) wasmTypes.Params { + return wasmTypes.Params{ + Block: wasmTypes.BlockInfo{ + Height: ctx.BlockHeight(), + Time: ctx.BlockTime().Unix(), + ChainID: ctx.ChainID(), + }, + Message: wasmTypes.MessageInfo{ + Signer: creator.String(), + SentFunds: NewWasmCoins(deposit), + }, + Contract: wasmTypes.ContractInfo{ + Address: contractAcct.GetAddress().String(), + Balance: NewWasmCoins(contractAcct.GetCoins()), + }, + } +} + +// NewWasmCoins translates between Cosmos SDK coins and Wasm coins +func NewWasmCoins(cosmosCoins sdk.Coins) (wasmCoins []wasmTypes.Coin) { + for _, coin := range cosmosCoins { + wasmCoin := wasmTypes.Coin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + wasmCoins = append(wasmCoins, wasmCoin) + } + return wasmCoins +} + +// NewContract creates a new instance of a given WASM contract +func NewContract(codeID uint64, creator sdk.AccAddress, initMsg []byte, prefixStore prefix.Store) Contract { + return Contract{ + CodeID: codeID, + Creator: creator, + InitMsg: initMsg, + PrefixStore: prefixStore, + } +} + +// CosmosResult converts from a Wasm Result type +func CosmosResult(wasmResult wasmTypes.Result) sdk.Result { + return sdk.Result{ + Data: []byte(wasmResult.Data), + Log: wasmResult.Log, + GasUsed: wasmResult.GasUsed, + } +} diff --git a/x/wasm/module.go b/x/wasm/module.go new file mode 100644 index 0000000000..905e918394 --- /dev/null +++ b/x/wasm/module.go @@ -0,0 +1,136 @@ +package wasm + +import ( + "encoding/json" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + // "github.com/cosmos/modules/incubator/wasm/client/cli" + // "github.com/cosmos/modules/incubator/wasm/client/rest" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic defines the basic application module used by the wasm module. +type AppModuleBasic struct{} + +// Name returns the wasm module's name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterCodec registers the wasm module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the wasm +// module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return ModuleCdc.MustMarshalJSON(&GenesisState{}) +} + +// ValidateGenesis performs genesis state validation for the wasm module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &data) + if err != nil { + return err + } + return ValidateGenesis(data) +} + +// RegisterRESTRoutes registers the REST routes for the wasm module. +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + // TODO + // rest.RegisterRoutes(ctx, rtr) +} + +// GetTxCmd returns the root tx command for the wasm module. +func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } + +// GetQueryCmd returns no root query command for the wasm module. +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + // TODO + // return cli.GetQueryCmd(cdc) + return nil +} + +//____________________________________________________________________________ + +// AppModule implements an application module for the wasm module. +type AppModule struct { + AppModuleBasic + keeper Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + } +} + +// Name returns the wasm module's name. +func (AppModule) Name() string { + return ModuleName +} + +// RegisterInvariants registers the wasm module invariants. +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +// Route returns the message routing key for the wasm module. +func (AppModule) Route() string { + return RouterKey +} + +// NewHandler returns an sdk.Handler for the wasm module. +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} + +// QuerierRoute returns the wasm module's querier route name. +func (AppModule) QuerierRoute() string { + return QuerierRoute +} + +// NewQuerierHandler returns the wasm module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} + +// InitGenesis performs genesis initialization for the wasm module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the wasm +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock returns the begin blocker for the wasm module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock returns the end blocker for the wasm module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go new file mode 100644 index 0000000000..310e278481 --- /dev/null +++ b/x/wasm/module_test.go @@ -0,0 +1,352 @@ +package wasm + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +type testData struct { + module module.AppModule + ctx sdk.Context + acctKeeper auth.AccountKeeper +} + +// returns a cleanup function, which must be defered on +func setupTest(t *testing.T) (testData, func()) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + + ctx, acctKeeper, keeper := CreateTestInput(t, false, tempDir) + data := testData{ + module: NewAppModule(keeper), + ctx: ctx, + acctKeeper: acctKeeper, + } + cleanup := func() { os.RemoveAll(tempDir) } + return data, cleanup +} + +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { + key := ed25519.GenPrivKey() + pub := key.PubKey() + addr := sdk.AccAddress(pub.Address()) + return key, pub, addr +} + +func mustLoad(path string) []byte { + bz, err := ioutil.ReadFile(path) + if err != nil { + panic(err) + } + return bz +} + +var ( + key1, pub1, addr1 = keyPubAddr() + testContract = mustLoad("./internal/keeper/testdata/contract.wasm") +) + +func TestHandleCreate(t *testing.T) { + cases := map[string]struct { + msg sdk.Msg + isValid bool + }{ + "empty": { + msg: MsgStoreCode{}, + isValid: false, + }, + "invalid wasm": { + msg: MsgStoreCode{ + Sender: addr1, + WASMByteCode: []byte("foobar"), + }, + isValid: false, + }, + "valid wasm": { + msg: MsgStoreCode{ + Sender: addr1, + WASMByteCode: testContract, + }, + isValid: true, + }, + } + + for name, tc := range cases { + tc := tc + t.Run(name, func(t *testing.T) { + data, cleanup := setupTest(t) + defer cleanup() + + h := data.module.NewHandler() + q := data.module.NewQuerierHandler() + + res := h(data.ctx, tc.msg) + if !tc.isValid { + require.False(t, res.IsOK(), "%#v", res) + assertCodeList(t, q, data.ctx, 0) + assertCodeBytes(t, q, data.ctx, 1, nil) + return + } + require.True(t, res.IsOK(), "%#v", res) + assertCodeList(t, q, data.ctx, 1) + assertCodeBytes(t, q, data.ctx, 1, testContract) + }) + } +} + +type initMsg struct { + Verifier string `json:"verifier"` + Beneficiary string `json:"beneficiary"` +} + +type state struct { + Verifier string `json:"verifier"` + Beneficiary string `json:"beneficiary"` + Funder string `json:"funder"` +} + +func TestHandleInstantiate(t *testing.T) { + data, cleanup := setupTest(t) + defer cleanup() + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + creator := createFakeFundedAccount(data.ctx, data.acctKeeper, deposit) + + h := data.module.NewHandler() + q := data.module.NewQuerierHandler() + + msg := MsgStoreCode{ + Sender: creator, + WASMByteCode: testContract, + } + res := h(data.ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, res.Data, []byte("1")) + + initMsg := initMsg{ + Verifier: "fred", + Beneficiary: "bob", + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + + // create with no balance is also legal + initCmd := MsgInstantiateContract{ + Sender: creator, + Code: 1, + InitMsg: initMsgBz, + InitFunds: nil, + } + res = h(data.ctx, initCmd) + require.True(t, res.IsOK()) + contractAddr := sdk.AccAddress(res.Data) + require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String()) + + assertCodeList(t, q, data.ctx, 1) + assertCodeBytes(t, q, data.ctx, 1, testContract) + + assertContractList(t, q, data.ctx, []string{contractAddr.String()}) + assertContractInfo(t, q, data.ctx, contractAddr, 1, creator) + assertContractState(t, q, data.ctx, contractAddr, state{ + Verifier: "fred", + Beneficiary: "bob", + Funder: creator.String(), + }) +} + +func TestHandleExecute(t *testing.T) { + data, cleanup := setupTest(t) + defer cleanup() + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) + creator := createFakeFundedAccount(data.ctx, data.acctKeeper, deposit.Add(deposit)) + fred := createFakeFundedAccount(data.ctx, data.acctKeeper, topUp) + + h := data.module.NewHandler() + q := data.module.NewQuerierHandler() + + msg := MsgStoreCode{ + Sender: creator, + WASMByteCode: testContract, + } + res := h(data.ctx, msg) + require.True(t, res.IsOK()) + require.Equal(t, res.Data, []byte("1")) + + _, _, bob := keyPubAddr() + initMsg := initMsg{ + Verifier: fred.String(), + Beneficiary: bob.String(), + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + + initCmd := MsgInstantiateContract{ + Sender: creator, + Code: 1, + InitMsg: initMsgBz, + InitFunds: deposit, + } + res = h(data.ctx, initCmd) + require.True(t, res.IsOK()) + contractAddr := sdk.AccAddress(res.Data) + require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String()) + + // ensure bob doesn't exist + bobAcct := data.acctKeeper.GetAccount(data.ctx, bob) + require.Nil(t, bobAcct) + + // ensure funder has reduced balance + creatorAcct := data.acctKeeper.GetAccount(data.ctx, creator) + require.NotNil(t, creatorAcct) + // we started at 2*deposit, should have spent one above + assert.Equal(t, deposit, creatorAcct.GetCoins()) + + // ensure contract has updated balance + contractAcct := data.acctKeeper.GetAccount(data.ctx, contractAddr) + require.NotNil(t, contractAcct) + assert.Equal(t, deposit, contractAcct.GetCoins()) + + execCmd := MsgExecuteContract{ + Sender: fred, + Contract: contractAddr, + Msg: []byte("{}"), + SentFunds: topUp, + } + res = h(data.ctx, execCmd) + require.True(t, res.IsOK()) + + // ensure bob now exists and got both payments released + bobAcct = data.acctKeeper.GetAccount(data.ctx, bob) + require.NotNil(t, bobAcct) + balance := bobAcct.GetCoins() + assert.Equal(t, deposit.Add(topUp), balance) + + // ensure contract has updated balance + contractAcct = data.acctKeeper.GetAccount(data.ctx, contractAddr) + require.NotNil(t, contractAcct) + assert.Equal(t, sdk.Coins(nil), contractAcct.GetCoins()) + + // ensure all contract state is as after init + assertCodeList(t, q, data.ctx, 1) + assertCodeBytes(t, q, data.ctx, 1, testContract) + + assertContractList(t, q, data.ctx, []string{contractAddr.String()}) + assertContractInfo(t, q, data.ctx, contractAddr, 1, creator) + assertContractState(t, q, data.ctx, contractAddr, state{ + Verifier: fred.String(), + Beneficiary: bob.String(), + Funder: creator.String(), + }) +} + +func assertCodeList(t *testing.T, q sdk.Querier, ctx sdk.Context, expectedNum int) { + bz, sdkerr := q(ctx, []string{QueryListCode}, abci.RequestQuery{}) + require.NoError(t, sdkerr) + + if len(bz) == 0 { + require.Equal(t, expectedNum, 0) + return + } + + var res []CodeInfo + err := json.Unmarshal(bz, &res) + require.NoError(t, err) + + assert.Equal(t, expectedNum, len(res)) +} + +type wasmCode struct { + Code []byte `json:"code", yaml:"code"` +} + +func assertCodeBytes(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, expectedBytes []byte) { + path := []string{QueryGetCode, fmt.Sprintf("%d", codeID)} + bz, sdkerr := q(ctx, path, abci.RequestQuery{}) + require.NoError(t, sdkerr) + + if len(bz) == 0 { + require.Equal(t, len(expectedBytes), 0) + return + } + + var res wasmCode + err := json.Unmarshal(bz, &res) + require.NoError(t, err) + + assert.Equal(t, expectedBytes, res.Code) +} + +func assertContractList(t *testing.T, q sdk.Querier, ctx sdk.Context, addrs []string) { + bz, sdkerr := q(ctx, []string{QueryListContracts}, abci.RequestQuery{}) + require.NoError(t, sdkerr) + + if len(bz) == 0 { + require.Equal(t, len(addrs), 0) + return + } + + var res []string + err := json.Unmarshal(bz, &res) + require.NoError(t, err) + + assert.Equal(t, addrs, res) +} + +type model struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func assertContractState(t *testing.T, q sdk.Querier, ctx sdk.Context, addr sdk.AccAddress, expected state) { + path := []string{QueryGetContractState, addr.String()} + bz, sdkerr := q(ctx, path, abci.RequestQuery{}) + require.NoError(t, sdkerr) + + var res []model + err := json.Unmarshal(bz, &res) + require.NoError(t, err) + require.Equal(t, 1, len(res), "#v", res) + require.Equal(t, "config", res[0].Key) + + expectedBz, err := json.Marshal(expected) + require.NoError(t, err) + assert.Equal(t, string(expectedBz), res[0].Value) +} + +func assertContractInfo(t *testing.T, q sdk.Querier, ctx sdk.Context, addr sdk.AccAddress, codeID uint64, creator sdk.AccAddress) { + path := []string{QueryGetContract, addr.String()} + bz, sdkerr := q(ctx, path, abci.RequestQuery{}) + require.NoError(t, sdkerr) + + var res Contract + err := json.Unmarshal(bz, &res) + require.NoError(t, err) + + assert.Equal(t, codeID, res.CodeID) + assert.Equal(t, creator, res.Creator) +} + +func createFakeFundedAccount(ctx sdk.Context, am auth.AccountKeeper, coins sdk.Coins) sdk.AccAddress { + _, _, addr := keyPubAddr() + baseAcct := auth.NewBaseAccountWithAddress(addr) + _ = baseAcct.SetCoins(coins) + am.SetAccount(ctx, &baseAcct) + + return addr +} From a1369d799fb557f262c8b424052c7a2f394f384a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 17:05:38 +0100 Subject: [PATCH 02/12] Update imports in x/wasmd --- x/wasm/alias.go | 8 ++++---- x/wasm/client/cli/tx.go | 2 +- x/wasm/genesis.go | 2 +- x/wasm/internal/keeper/keeper.go | 2 +- x/wasm/internal/keeper/querier.go | 2 +- x/wasm/module.go | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x/wasm/alias.go b/x/wasm/alias.go index b89d12837f..59b0a49ffc 100644 --- a/x/wasm/alias.go +++ b/x/wasm/alias.go @@ -2,14 +2,14 @@ package wasm import ( - "github.com/cosmos/modules/incubator/wasm/internal/keeper" - "github.com/cosmos/modules/incubator/wasm/internal/types" + "github.com/cosmwasm/wasmd/x/wasm/internal/keeper" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" ) // autogenerated code using github.com/rigelrozanski/multitool // aliases generated for the following subdirectories: -// ALIASGEN: github.com/cosmos/modules/incubator/wasm/internal/types/ -// ALIASGEN: github.com/cosmos/modules/incubator/wasm/internal/keeper/ +// ALIASGEN: github.com/cosmwasm/wasmd/x/wasm/internal/types/ +// ALIASGEN: github.com/cosmwasm/wasmd/x/wasm/internal/keeper/ const ( DefaultCodespace = types.DefaultCodespace diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index 29878fd7b9..84890e013f 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -14,7 +14,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/modules/incubator/wasm/internal/types" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" ) const ( diff --git a/x/wasm/genesis.go b/x/wasm/genesis.go index 1d6e31d999..33e4f4abbb 100644 --- a/x/wasm/genesis.go +++ b/x/wasm/genesis.go @@ -3,7 +3,7 @@ package wasm import ( sdk "github.com/cosmos/cosmos-sdk/types" // authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" - // "github.com/cosmos/modules/incubator/wasm/internal/types" + // "github.com/cosmwasm/wasmd/x/wasm/internal/types" ) type GenesisState struct { diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 882b13e15a..f1c7f696c5 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -15,7 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/tendermint/tendermint/crypto" - "github.com/cosmos/modules/incubator/wasm/internal/types" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" ) // GasMultiplier is how many cosmwasm gas points = 1 sdk gas point diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index e96969eaf1..bc880d5cf6 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -7,7 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/tendermint/abci/types" - "github.com/cosmos/modules/incubator/wasm/internal/types" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" ) const ( diff --git a/x/wasm/module.go b/x/wasm/module.go index 905e918394..db8276d0d5 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -12,8 +12,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - // "github.com/cosmos/modules/incubator/wasm/client/cli" - // "github.com/cosmos/modules/incubator/wasm/client/rest" + // "github.com/cosmwasm/wasmd/x/wasm/client/cli" + // "github.com/cosmwasm/wasmd/x/wasm/client/rest" ) var ( From d87acdcf2da23126e6da0ab3921ea397505c8567 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 17:13:44 +0100 Subject: [PATCH 03/12] Update go.mod with proper deps --- go.mod | 2 ++ go.sum | 2 ++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index d1d0ee72d4..c369a88b1e 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,11 @@ go 1.13 require ( github.com/btcsuite/btcd v0.0.0-20190807005414-4063feeff79a // indirect + github.com/confio/go-cosmwasm v0.3.3 github.com/cosmos/cosmos-sdk v0.34.4-0.20191114141721-d4c831e63ad3 github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect github.com/golang/mock v1.3.1 // indirect + github.com/gorilla/mux v1.7.3 github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/gomega v1.5.0 // indirect github.com/otiai10/copy v1.0.2 diff --git a/go.sum b/go.sum index cb9df7b1c9..cb3a2d75c1 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/confio/go-cosmwasm v0.3.3 h1:kW7BFfUWjMJ0bLbircMkTWQaYjKkryuyDdfPr606VA0= +github.com/confio/go-cosmwasm v0.3.3/go.mod h1:pHipRby+f3cv97QPLELkzOAlNs/s87uDyhc+SnMn7L4= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= From 6ddd6377334ecf53f6a037248b5b4f386350b9f4 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 17:17:10 +0100 Subject: [PATCH 04/12] Update code to master --- x/wasm/client/cli/tx.go | 12 ++++++------ x/wasm/internal/types/types.go | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index 84890e013f..fe94390072 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -2,17 +2,17 @@ package client import ( "bufio" - "strconv" + // "strconv" "io/ioutil" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - auth "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" "github.com/cosmwasm/wasmd/x/wasm/internal/types" ) @@ -25,11 +25,11 @@ const ( // GetTxCmd returns the transaction commands for this module func GetTxCmd(cdc *codec.Codec) *cobra.Command { txCmd := &cobra.Command{ - Use: types.ModuleName, - Short: "Wasm transaction subcommands", + Use: types.ModuleName, + Short: "Wasm transaction subcommands", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, - RunE: utils.ValidateCmd, + RunE: client.ValidateCmd, } txCmd.AddCommand( StoreCodeCmd(cdc), diff --git a/x/wasm/internal/types/types.go b/x/wasm/internal/types/types.go index 43283d64f8..8ed0d20f49 100644 --- a/x/wasm/internal/types/types.go +++ b/x/wasm/internal/types/types.go @@ -4,7 +4,7 @@ import ( wasmTypes "github.com/confio/go-cosmwasm/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" + auth "github.com/cosmos/cosmos-sdk/x/auth/exported" ) // CodeInfo is data for the uploaded contract WASM code @@ -73,8 +73,8 @@ func NewContract(codeID uint64, creator sdk.AccAddress, initMsg []byte, prefixSt // CosmosResult converts from a Wasm Result type func CosmosResult(wasmResult wasmTypes.Result) sdk.Result { return sdk.Result{ - Data: []byte(wasmResult.Data), - Log: wasmResult.Log, + Data: []byte(wasmResult.Data), + Log: wasmResult.Log, GasUsed: wasmResult.GasUsed, } } From 0b74b12b0eeb0d102bff5bc02571a8660c6d9bcc Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 17:30:33 +0100 Subject: [PATCH 05/12] Complete tx command and wire to module --- x/wasm/client/cli/tx.go | 174 +++++++++++++++++++--------------------- x/wasm/module.go | 6 +- 2 files changed, 88 insertions(+), 92 deletions(-) diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index fe94390072..2780154239 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -1,11 +1,12 @@ -package client +package cli import ( "bufio" - // "strconv" "io/ioutil" + "strconv" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" @@ -31,11 +32,11 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } - txCmd.AddCommand( + txCmd.AddCommand(client.PostCommands( StoreCodeCmd(cdc), - // InstantiateContractCmd(cdc), - // ExecuteContractCmd(cdc), - ) + InstantiateContractCmd(cdc), + ExecuteContractCmd(cdc), + )...) return txCmd } @@ -64,92 +65,85 @@ func StoreCodeCmd(cdc *codec.Codec) *cobra.Command { return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } + return cmd +} + +// InstantiateContractCmd will instantiate a contract from previously uploaded code. +func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "create [from_key_or_address] [code_id_int64] [json_encoded_init_args]", + Short: "Instantiate a wasm contract", + 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.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc) - cmd = client.PostCommands(cmd)[0] + // get the id of the code to instantiate + codeID, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + amounstStr := viper.GetString(flagAmount) + amount, err := sdk.ParseCoins(amounstStr) + if err != nil { + return err + } + + initMsg := args[2] + + // build and sign the transaction, then broadcast to Tendermint + msg := types.MsgInstantiateContract{ + Sender: cliCtx.GetFromAddress(), + Code: codeID, + InitFunds: amount, + InitMsg: []byte(initMsg), + } + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + + cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") return cmd } -// // InstantiateContractCmd will instantiate a contract from previously uploaded code. -// func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command { -// cmd := &cobra.Command{ -// Use: "create [from_key_or_address] [code_id_int64] [coins] [json_encoded_init_args]", -// Short: "Instantiate a wasm contract", -// Args: cobra.ExactArgs(4), -// RunE: func(cmd *cobra.Command, args []string) error { -// txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) -// cliCtx := context.NewCLIContextWithFrom(args[0]). -// WithCodec(cdc). -// WithAccountDecoder(cdc) - -// // get the id of the code to instantiate -// codeID, err := strconv.Atoi(args[1]) -// if err != nil { -// return err -// } - -// // parse coins trying to be sent -// coins, err := sdk.ParseCoins(args[2]) -// if err != nil { -// return err -// } - -// initMsg := args[3] - -// // build and sign the transaction, then broadcast to Tendermint -// msg := MsgCreateContract{ -// Sender: cliCtx.GetFromAddress(), -// Code: CodeID(codeID), -// InitFunds: coins, -// InitMsg: []byte(initMsg), -// } -// return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) -// }, -// } - -// cmd = client.PostCommands(cmd)[0] - -// return cmd -// } - -// // ExecuteContractCmd will instantiate a contract from previously uploaded code. -// func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command { -// cmd := &cobra.Command{ -// Use: "send [from_key_or_address] [contract_addr_bech32] [coins] [json_encoded_send_args]", -// Short: "Instantiate a wasm contract", -// Args: cobra.ExactArgs(4), -// RunE: func(cmd *cobra.Command, args []string) error { -// txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) -// cliCtx := context.NewCLIContextWithFrom(args[0]). -// WithCodec(cdc). -// WithAccountDecoder(cdc) - -// // get the id of the code to instantiate -// contractAddr, err := sdk.AccAddressFromBech32(args[1]) -// if err != nil { -// return err -// } - -// // parse coins trying to be sent -// coins, err := sdk.ParseCoins(args[2]) -// if err != nil { -// return err -// } - -// sendMsg := args[3] - -// // build and sign the transaction, then broadcast to Tendermint -// msg := MsgSendContract{ -// Sender: cliCtx.GetFromAddress(), -// Contract: contractAddr, -// Payment: coins, -// Msg: []byte(sendMsg), -// } -// return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) -// }, -// } - -// cmd = client.PostCommands(cmd)[0] - -// return cmd -// } +// ExecuteContractCmd will instantiate a contract from previously uploaded code. +func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "send [from_key_or_address] [contract_addr_bech32] [json_encoded_send_args]", + Short: "Execute a command on a wasm contract", + 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.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc) + + // get the id of the code to instantiate + contractAddr, err := sdk.AccAddressFromBech32(args[1]) + if err != nil { + return err + } + + amounstStr := viper.GetString(flagAmount) + amount, err := sdk.ParseCoins(amounstStr) + if err != nil { + return err + } + + execMsg := args[3] + + // build and sign the transaction, then broadcast to Tendermint + msg := types.MsgExecuteContract{ + Sender: cliCtx.GetFromAddress(), + Contract: contractAddr, + SentFunds: amount, + Msg: []byte(execMsg), + } + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + + cmd.Flags().String(flagAmount, "", "Coins to send to the contract along with command") + return cmd +} diff --git a/x/wasm/module.go b/x/wasm/module.go index db8276d0d5..19863cb828 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -12,7 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - // "github.com/cosmwasm/wasmd/x/wasm/client/cli" + "github.com/cosmwasm/wasmd/x/wasm/client/cli" // "github.com/cosmwasm/wasmd/x/wasm/client/rest" ) @@ -57,7 +57,9 @@ func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router } // GetTxCmd returns the root tx command for the wasm module. -func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(cdc) +} // GetQueryCmd returns no root query command for the wasm module. func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { From 4a77f43b63ba063831c191f45a11e24b5db44b1e Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 17:50:10 +0100 Subject: [PATCH 06/12] Add query command for wasm code --- x/wasm/alias.go | 27 +++++----- x/wasm/client/cli/query.go | 89 +++++++++++++++++++++++++++++++ x/wasm/internal/keeper/querier.go | 4 +- x/wasm/module.go | 4 +- x/wasm/module_test.go | 6 +-- 5 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 x/wasm/client/cli/query.go diff --git a/x/wasm/alias.go b/x/wasm/alias.go index 59b0a49ffc..5281ae3f99 100644 --- a/x/wasm/alias.go +++ b/x/wasm/alias.go @@ -8,10 +8,17 @@ import ( // autogenerated code using github.com/rigelrozanski/multitool // aliases generated for the following subdirectories: -// ALIASGEN: github.com/cosmwasm/wasmd/x/wasm/internal/types/ // ALIASGEN: github.com/cosmwasm/wasmd/x/wasm/internal/keeper/ +// ALIASGEN: github.com/cosmwasm/wasmd/x/wasm/internal/types/ const ( + GasMultiplier = keeper.GasMultiplier + MaxGas = keeper.MaxGas + QueryListContracts = keeper.QueryListContracts + QueryGetContract = keeper.QueryGetContract + QueryGetContractState = keeper.QueryGetContractState + QueryGetCode = keeper.QueryGetCode + QueryListCode = keeper.QueryListCode DefaultCodespace = types.DefaultCodespace CodeCreatedFailed = types.CodeCreatedFailed CodeAccountExists = types.CodeAccountExists @@ -24,17 +31,14 @@ const ( QuerierRoute = types.QuerierRoute RouterKey = types.RouterKey MaxWasmSize = types.MaxWasmSize - GasMultiplier = keeper.GasMultiplier - MaxGas = keeper.MaxGas - QueryListContracts = keeper.QueryListContracts - QueryGetContract = keeper.QueryGetContract - QueryGetContractState = keeper.QueryGetContractState - QueryGetCode = keeper.QueryGetCode - QueryListCode = keeper.QueryListCode ) var ( // functions aliases + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + MakeTestCodec = keeper.MakeTestCodec + CreateTestInput = keeper.CreateTestInput RegisterCodec = types.RegisterCodec ErrCreateFailed = types.ErrCreateFailed ErrAccountExists = types.ErrAccountExists @@ -49,10 +53,6 @@ var ( NewWasmCoins = types.NewWasmCoins NewContract = types.NewContract CosmosResult = types.CosmosResult - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier - MakeTestCodec = keeper.MakeTestCodec - CreateTestInput = keeper.CreateTestInput // variable aliases ModuleCdc = types.ModuleCdc @@ -64,10 +64,11 @@ var ( ) type ( + Keeper = keeper.Keeper + GetCodeResponse = keeper.GetCodeResponse MsgStoreCode = types.MsgStoreCode MsgInstantiateContract = types.MsgInstantiateContract MsgExecuteContract = types.MsgExecuteContract CodeInfo = types.CodeInfo Contract = types.Contract - Keeper = keeper.Keeper ) diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go new file mode 100644 index 0000000000..ad0f0566f9 --- /dev/null +++ b/x/wasm/client/cli/query.go @@ -0,0 +1,89 @@ +package cli + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strconv" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmwasm/wasmd/x/wasm/internal/keeper" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" +) + +func GetQueryCmd(cdc *codec.Codec) *cobra.Command { + queryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the wasm module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + queryCmd.AddCommand(client.GetCommands( + GetCmdListCode(cdc), + GetCmdQueryCode(cdc), + )...) + return queryCmd +} + +// GetCmdListCode lists all wasm code uploaded +func GetCmdListCode(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "list-code", + Short: "List all wasm bytecode on the chain", + Long: "List all wasm bytecode on the chain", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListCode) + res, _, err := cliCtx.Query(route) + if err != nil { + return err + } + fmt.Println(res) + return nil + }, + } +} + +// GetCmdQueryCode returns the bytecode for a given contract +func GetCmdQueryCode(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "code [code_id] [output filename]", + Short: "Downloads wasm bytecode for given code id", + Long: "Downloads wasm bytecode for given code id", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + codeID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/%s/%d", types.QuerierRoute, keeper.QueryGetCode, codeID) + res, _, err := cliCtx.Query(route) + if err != nil { + return err + } + + if len(res) == 0 { + return fmt.Errorf("contract not found") + } + var code keeper.GetCodeResponse + err = json.Unmarshal(res, &code) + if err != nil { + return err + } + + fmt.Printf("Writing wasm code to %s\n", args[1]) + return ioutil.WriteFile(args[1], code.Code, 0644) + }, + } +} diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index bc880d5cf6..c5163a7b87 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -93,7 +93,7 @@ func queryContractState(ctx sdk.Context, bech string, req abci.RequestQuery, kee return bz, nil } -type wasmCode struct { +type GetCodeResponse struct { Code []byte `json:"code", yaml:"code"` } @@ -108,7 +108,7 @@ func queryCode(ctx sdk.Context, codeIDstr string, req abci.RequestQuery, keeper return nil, sdk.ErrUnknownRequest("loading wasm code: " + err.Error()) } - bz, err := json.MarshalIndent(wasmCode{code}, "", " ") + bz, err := json.MarshalIndent(GetCodeResponse{code}, "", " ") if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) } diff --git a/x/wasm/module.go b/x/wasm/module.go index 19863cb828..5291feb205 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -63,9 +63,7 @@ func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { // GetQueryCmd returns no root query command for the wasm module. func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { - // TODO - // return cli.GetQueryCmd(cdc) - return nil + return cli.GetQueryCmd(cdc) } //____________________________________________________________________________ diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index 310e278481..45364e572a 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -271,10 +271,6 @@ func assertCodeList(t *testing.T, q sdk.Querier, ctx sdk.Context, expectedNum in assert.Equal(t, expectedNum, len(res)) } -type wasmCode struct { - Code []byte `json:"code", yaml:"code"` -} - func assertCodeBytes(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, expectedBytes []byte) { path := []string{QueryGetCode, fmt.Sprintf("%d", codeID)} bz, sdkerr := q(ctx, path, abci.RequestQuery{}) @@ -285,7 +281,7 @@ func assertCodeBytes(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64 return } - var res wasmCode + var res GetCodeResponse err := json.Unmarshal(bz, &res) require.NoError(t, err) From d79ff0cd191b66a7297985d050557ed2b1e151a9 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 17:57:24 +0100 Subject: [PATCH 07/12] Finalize the query cli commands for x/wasm --- x/wasm/client/cli/query.go | 79 +++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index ad0f0566f9..3d8a4bf204 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmwasm/wasmd/x/wasm/internal/keeper" "github.com/cosmwasm/wasmd/x/wasm/internal/types" @@ -27,6 +28,9 @@ func GetQueryCmd(cdc *codec.Codec) *cobra.Command { queryCmd.AddCommand(client.GetCommands( GetCmdListCode(cdc), GetCmdQueryCode(cdc), + GetCmdListContracts(cdc), + GetCmdGetContractInfo(cdc), + GetCmdGetContractState(cdc), )...) return queryCmd } @@ -82,8 +86,81 @@ func GetCmdQueryCode(cdc *codec.Codec) *cobra.Command { return err } - fmt.Printf("Writing wasm code to %s\n", args[1]) + fmt.Printf("Downloading wasm code to %s\n", args[1]) return ioutil.WriteFile(args[1], code.Code, 0644) }, } } + +// GetCmdListContracts lists all instantiated contracts +func GetCmdListContracts(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "list-contracts", + Short: "List addresses of all instantiated contracts on the chain", + Long: "List addresses of all instantiated contracts on the chain", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListContracts) + res, _, err := cliCtx.Query(route) + if err != nil { + return err + } + fmt.Println(res) + return nil + }, + } +} + +// GetCmdGetContractInfo gets details about a given contract +func GetCmdGetContractInfo(cdc *codec.Codec) *cobra.Command { + return &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", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContract, addr.String()) + res, _, err := cliCtx.Query(route) + if err != nil { + return err + } + fmt.Println(res) + return nil + }, + } +} + +// GetCmdGetContractState dumps full internal state of a given contract +func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "contract-state [bech32_address]", + Short: "Prints out internal state of a contract given its address", + Long: "Prints out internal state of a contract given its address", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String()) + res, _, err := cliCtx.Query(route) + if err != nil { + return err + } + fmt.Println(res) + return nil + }, + } +} From 35aab0ee6567065b62438c4529b4985b21675419 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 18:15:14 +0100 Subject: [PATCH 08/12] Wire up wasm module to the application --- app/app.go | 20 ++++++++++++++++++-- cmd/wasmd/main.go | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/app.go b/app/app.go index 6494df7f9e..584707d09f 100644 --- a/app/app.go +++ b/app/app.go @@ -3,8 +3,12 @@ package app import ( "io" "os" + "path/filepath" + + "github.com/spf13/viper" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/cli" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" @@ -29,6 +33,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" + + "github.com/cosmwasm/wasmd/x/wasm" ) const appName = "WasmApp" @@ -52,6 +58,7 @@ var ( distr.AppModuleBasic{}, gov.NewAppModuleBasic(paramsclient.ProposalHandler, distr.ProposalHandler), params.AppModuleBasic{}, + wasm.AppModuleBasic{}, crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, supply.AppModuleBasic{}, @@ -106,6 +113,7 @@ type WasmApp struct { crisisKeeper crisis.Keeper paramsKeeper params.Keeper evidenceKeeper *evidence.Keeper + wasmKeeper wasm.Keeper // the module manager mm *module.Manager @@ -129,7 +137,7 @@ func NewWasmApp( keys := sdk.NewKVStoreKeys( bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, gov.StoreKey, - params.StoreKey, evidence.StoreKey, + params.StoreKey, evidence.StoreKey, wasm.StoreKey, ) tKeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) @@ -168,6 +176,13 @@ func NewWasmApp( ) app.crisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.supplyKeeper, auth.FeeCollectorName) + // just re-use the full router - do we want to limit this more? + var wasmRouter = bApp.Router() + // better way to get this dir??? + homeDir := viper.GetString(cli.HomeFlag) + wasmDir := filepath.Join(homeDir, "wasm") + app.wasmKeeper = wasm.NewKeeper(app.cdc, keys[wasm.StoreKey], app.accountKeeper, app.bankKeeper, wasmRouter, wasmDir) + // create evidence keeper with evidence router app.evidenceKeeper = evidence.NewKeeper( app.cdc, keys[evidence.StoreKey], evidenceSubspace, evidence.DefaultCodespace, @@ -206,6 +221,7 @@ func NewWasmApp( slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), evidence.NewAppModule(*app.evidenceKeeper), + wasm.NewAppModule(app.wasmKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -220,7 +236,7 @@ func NewWasmApp( app.mm.SetOrderInitGenesis( distr.ModuleName, staking.ModuleName, auth.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, - crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, + crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, wasm.ModuleName, ) app.mm.RegisterInvariants(&app.crisisKeeper) diff --git a/cmd/wasmd/main.go b/cmd/wasmd/main.go index ceee7bf7e7..d37ad4ca8c 100644 --- a/cmd/wasmd/main.go +++ b/cmd/wasmd/main.go @@ -60,7 +60,7 @@ func main() { rootCmd.AddCommand(genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics)) rootCmd.AddCommand(AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome)) rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true)) - rootCmd.AddCommand(testnetCmd(ctx, cdc, app.ModuleBasics, auth.GenesisAccountIterator{})) + // rootCmd.AddCommand(testnetCmd(ctx, cdc, app.ModuleBasics, auth.GenesisAccountIterator{})) rootCmd.AddCommand(replayCmd()) rootCmd.AddCommand(debug.Cmd(cdc)) From 5f304e335c65b7ce33784da5d75fa5dbeebf6e2d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 19:00:20 +0100 Subject: [PATCH 09/12] First run-through documented, along with TODOs --- docs/deploy-testnet.md | 49 ++++++++++++++++++++++++++++++++++++++ x/wasm/client/cli/query.go | 8 +++---- x/wasm/handler.go | 12 +++++++--- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/docs/deploy-testnet.md b/docs/deploy-testnet.md index 4327604b6e..765d25e63b 100644 --- a/docs/deploy-testnet.md +++ b/docs/deploy-testnet.md @@ -59,6 +59,55 @@ wasmd start This setup puts all the data for `wasmd` in `~/.wasmd`. You can examine the genesis file you created at `~/.wasmd/config/genesis.json`. With this configuration `wasmcli` is also ready to use and has an account with tokens (both staking and custom). +### Set up client + +```bash +wasmcli config chain-id testing +wasmcli config trust-node true +wasmcli config node tcp://localhost:26657 +wasmcli config output json +wasmcli config indent true + +# verify initial setup +wasmcli query account $(wasmcli keys show validator -a) +wasmcli query wasm list-code +wasmcli query wasm list-contracts + +# upload a contract and verify +cp $HOME/go/src/github.com/cosmwasm/wasmd/x/wasm/internal/keeper/testdata/contract.wasm upload.wasm +wasmcli tx wasm store validator upload.wasm --gas 800000 +# TODO: add id to json output +wasmcli query wasm list-code +wasmcli query wasm code 1 download.wasm +sha256sum upload.wasm download.wasm + +# prepare more accounts +wasmcli keys add fred +wasmcli keys add bob +wasmcli tx send $(wasmcli keys show validator -a) $(wasmcli keys show fred -a) 98765stake +wasmcli query account $(wasmcli keys show fred -a) +wasmcli query account $(wasmcli keys show bob -a) + +# instantiate contract and verify +INIT="{\"verifier\":\"$(wasmcli keys show fred -a)\", \"beneficiary\":\"$(wasmcli keys show fred -a)\"}" +wasmcli tx wasm create validator 1 "$INIT" --amount=50000stake +wasmcli query wasm list-contracts +CONTRACT=cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5 +# TODO: remove prefix store - make init_msg string +wasmcli query wasm contract $CONTRACT +wasmcli query wasm contract-state $CONTRACT +wasmcli query account $CONTRACT + +# execute fails if wrong person +# TODO: make exec - fix logic +wasmcli tx wasm send validator $CONTRACT "{}" +wasmcli query account $(wasmcli keys show bob -a) + +wasmcli tx wasm send fred $CONTRACT "{}" +wasmcli query account $(wasmcli keys show bob -a) +wasmcli query account $CONTRACT +``` + ## Multi-node, Local, Automated Testnet From the [networks/local directory](https://github.com/cosmwasm/wasmd/tree/master/networks/local): diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index 3d8a4bf204..ab2b5c64e2 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -50,7 +50,7 @@ func GetCmdListCode(cdc *codec.Codec) *cobra.Command { if err != nil { return err } - fmt.Println(res) + fmt.Println(string(res)) return nil }, } @@ -107,7 +107,7 @@ func GetCmdListContracts(cdc *codec.Codec) *cobra.Command { if err != nil { return err } - fmt.Println(res) + fmt.Println(string(res)) return nil }, } @@ -133,7 +133,7 @@ func GetCmdGetContractInfo(cdc *codec.Codec) *cobra.Command { if err != nil { return err } - fmt.Println(res) + fmt.Println(string(res)) return nil }, } @@ -159,7 +159,7 @@ func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command { if err != nil { return err } - fmt.Println(res) + fmt.Println(string(res)) return nil }, } diff --git a/x/wasm/handler.go b/x/wasm/handler.go index a01ca8e7d7..7078bfcf28 100644 --- a/x/wasm/handler.go +++ b/x/wasm/handler.go @@ -18,12 +18,18 @@ func NewHandler(k Keeper) sdk.Handler { switch msg := msg.(type) { case MsgStoreCode: + return handleStoreCode(ctx, k, &msg) + case *MsgStoreCode: return handleStoreCode(ctx, k, msg) case MsgInstantiateContract: + return handleInstantiate(ctx, k, &msg) + case *MsgInstantiateContract: return handleInstantiate(ctx, k, msg) case MsgExecuteContract: + return handleExecute(ctx, k, &msg) + case *MsgExecuteContract: return handleExecute(ctx, k, msg) default: @@ -33,7 +39,7 @@ func NewHandler(k Keeper) sdk.Handler { } } -func handleStoreCode(ctx sdk.Context, k Keeper, msg MsgStoreCode) sdk.Result { +func handleStoreCode(ctx sdk.Context, k Keeper, msg *MsgStoreCode) sdk.Result { codeID, err := k.Create(ctx, msg.Sender, msg.WASMByteCode) if err != nil { return err.Result() @@ -55,7 +61,7 @@ func handleStoreCode(ctx sdk.Context, k Keeper, msg MsgStoreCode) sdk.Result { } } -func handleInstantiate(ctx sdk.Context, k Keeper, msg MsgInstantiateContract) sdk.Result { +func handleInstantiate(ctx sdk.Context, k Keeper, msg *MsgInstantiateContract) sdk.Result { contractAddr, err := k.Instantiate(ctx, msg.Sender, msg.Code, msg.InitMsg, msg.InitFunds) if err != nil { return err.Result() @@ -78,7 +84,7 @@ func handleInstantiate(ctx sdk.Context, k Keeper, msg MsgInstantiateContract) sd } } -func handleExecute(ctx sdk.Context, k Keeper, msg MsgExecuteContract) sdk.Result { +func handleExecute(ctx sdk.Context, k Keeper, msg *MsgExecuteContract) sdk.Result { res, err := k.Execute(ctx, msg.Contract, msg.Sender, msg.SentFunds, msg.Msg) if err != nil { return err.Result() From 9f0304567d4f790d709e1ece33bd2fce9f20eb39 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 19:07:57 +0100 Subject: [PATCH 10/12] Finish full exec flow --- docs/deploy-testnet.md | 13 ++++++++----- x/wasm/client/cli/tx.go | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/deploy-testnet.md b/docs/deploy-testnet.md index 765d25e63b..4f0d48ea21 100644 --- a/docs/deploy-testnet.md +++ b/docs/deploy-testnet.md @@ -89,8 +89,9 @@ wasmcli query account $(wasmcli keys show fred -a) wasmcli query account $(wasmcli keys show bob -a) # instantiate contract and verify -INIT="{\"verifier\":\"$(wasmcli keys show fred -a)\", \"beneficiary\":\"$(wasmcli keys show fred -a)\"}" -wasmcli tx wasm create validator 1 "$INIT" --amount=50000stake +INIT="{\"verifier\":\"$(wasmcli keys show fred -a)\", \"beneficiary\":\"$(wasmcli keys show bob -a)\"}" +wasmcli tx wasm instantiate validator 1 "$INIT" --amount=50000stake +sleep 3 wasmcli query wasm list-contracts CONTRACT=cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5 # TODO: remove prefix store - make init_msg string @@ -99,11 +100,13 @@ wasmcli query wasm contract-state $CONTRACT wasmcli query account $CONTRACT # execute fails if wrong person -# TODO: make exec - fix logic -wasmcli tx wasm send validator $CONTRACT "{}" +wasmcli tx wasm execute validator $CONTRACT "{}" +sleep 3 +wasmcli query tx wasmcli query account $(wasmcli keys show bob -a) -wasmcli tx wasm send fred $CONTRACT "{}" +wasmcli tx wasm execute fred $CONTRACT "{}" +sleep 3 wasmcli query account $(wasmcli keys show bob -a) wasmcli query account $CONTRACT ``` diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index 2780154239..9d025e1715 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -71,7 +71,7 @@ func StoreCodeCmd(cdc *codec.Codec) *cobra.Command { // InstantiateContractCmd will instantiate a contract from previously uploaded code. func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "create [from_key_or_address] [code_id_int64] [json_encoded_init_args]", + Use: "instantiate [from_key_or_address] [code_id_int64] [json_encoded_init_args]", Short: "Instantiate a wasm contract", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { @@ -111,7 +111,7 @@ func InstantiateContractCmd(cdc *codec.Codec) *cobra.Command { // ExecuteContractCmd will instantiate a contract from previously uploaded code. func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "send [from_key_or_address] [contract_addr_bech32] [json_encoded_send_args]", + Use: "execute [from_key_or_address] [contract_addr_bech32] [json_encoded_send_args]", Short: "Execute a command on a wasm contract", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { @@ -131,7 +131,7 @@ func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command { return err } - execMsg := args[3] + execMsg := args[2] // build and sign the transaction, then broadcast to Tendermint msg := types.MsgExecuteContract{ From c164ffdce103bd0203d5270fcade04a4ad69c835 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 19:15:14 +0100 Subject: [PATCH 11/12] Clean up data types for queries --- x/wasm/internal/keeper/keeper.go | 2 +- x/wasm/internal/keeper/querier.go | 16 ++++++++++++++-- x/wasm/internal/types/types.go | 17 +++++++---------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index f1c7f696c5..11afd63020 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -118,7 +118,7 @@ func (k Keeper) Instantiate(ctx sdk.Context, creator sdk.AccAddress, codeID uint } // persist instance - instance := types.NewContract(codeID, creator, initMsg, prefixStore) + instance := types.NewContract(codeID, creator, string(initMsg)) // 0x02 | contractAddress (sdk.AccAddress) -> Instance store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(instance)) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index c5163a7b87..8cdd5ee037 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -5,7 +5,9 @@ import ( "strconv" sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/cosmwasm/wasmd/x/wasm/internal/types" ) @@ -115,8 +117,14 @@ func queryCode(ctx sdk.Context, codeIDstr string, req abci.RequestQuery, keeper return bz, nil } +type ListCodeResponse struct { + ID uint64 `json:"id"` + Creator sdk.AccAddress `json:"creator"` + CodeHash cmn.HexBytes `json:"code_hash"` +} + func queryCodeList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var info []*types.CodeInfo + var info []ListCodeResponse i := uint64(1) for true { @@ -125,7 +133,11 @@ func queryCodeList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byt if res == nil { break } - info = append(info, res) + info = append(info, ListCodeResponse{ + ID: i, + Creator: res.Creator, + CodeHash: res.CodeHash, + }) } bz, err := json.MarshalIndent(info, "", " ") diff --git a/x/wasm/internal/types/types.go b/x/wasm/internal/types/types.go index 8ed0d20f49..74018ac2fb 100644 --- a/x/wasm/internal/types/types.go +++ b/x/wasm/internal/types/types.go @@ -2,7 +2,6 @@ package types import ( wasmTypes "github.com/confio/go-cosmwasm/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth/exported" ) @@ -23,10 +22,9 @@ func NewCodeInfo(codeHash []byte, creator sdk.AccAddress) CodeInfo { // Contract stores a WASM contract instance type Contract struct { - CodeID uint64 `json:"code_id"` - Creator sdk.AccAddress `json:"creator"` - InitMsg []byte `json:"init_msg"` - PrefixStore prefix.Store `json:"prefix_store"` + CodeID uint64 `json:"code_id"` + Creator sdk.AccAddress `json:"creator"` + InitMsg string `json:"init_msg"` } // NewParams initializes params for a contract instance @@ -61,12 +59,11 @@ func NewWasmCoins(cosmosCoins sdk.Coins) (wasmCoins []wasmTypes.Coin) { } // NewContract creates a new instance of a given WASM contract -func NewContract(codeID uint64, creator sdk.AccAddress, initMsg []byte, prefixStore prefix.Store) Contract { +func NewContract(codeID uint64, creator sdk.AccAddress, initMsg string) Contract { return Contract{ - CodeID: codeID, - Creator: creator, - InitMsg: initMsg, - PrefixStore: prefixStore, + CodeID: codeID, + Creator: creator, + InitMsg: initMsg, } } From a3e7c30a2bde937fa1e9afd604dc6733140b46cd Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Nov 2019 19:26:39 +0100 Subject: [PATCH 12/12] Last query cleanup --- docs/deploy-testnet.md | 3 +-- x/wasm/client/cli/query.go | 4 ++++ x/wasm/internal/keeper/querier.go | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/deploy-testnet.md b/docs/deploy-testnet.md index 4f0d48ea21..bee5e983ac 100644 --- a/docs/deploy-testnet.md +++ b/docs/deploy-testnet.md @@ -76,7 +76,7 @@ wasmcli query wasm list-contracts # upload a contract and verify cp $HOME/go/src/github.com/cosmwasm/wasmd/x/wasm/internal/keeper/testdata/contract.wasm upload.wasm wasmcli tx wasm store validator upload.wasm --gas 800000 -# TODO: add id to json output +# TODO: stops after one hit wasmcli query wasm list-code wasmcli query wasm code 1 download.wasm sha256sum upload.wasm download.wasm @@ -94,7 +94,6 @@ wasmcli tx wasm instantiate validator 1 "$INIT" --amount=50000stake sleep 3 wasmcli query wasm list-contracts CONTRACT=cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5 -# TODO: remove prefix store - make init_msg string wasmcli query wasm contract $CONTRACT wasmcli query wasm contract-state $CONTRACT wasmcli query account $CONTRACT diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index ab2b5c64e2..cf6cf7332f 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -86,6 +86,10 @@ func GetCmdQueryCode(cdc *codec.Codec) *cobra.Command { return err } + if len(code.Code) == 0 { + return fmt.Errorf("contract not found") + } + fmt.Printf("Downloading wasm code to %s\n", args[1]) return ioutil.WriteFile(args[1], code.Code, 0644) }, diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 8cdd5ee037..282c3164b6 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -126,10 +126,10 @@ type ListCodeResponse struct { func queryCodeList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { var info []ListCodeResponse - i := uint64(1) + var i uint64 for true { - res := keeper.GetCodeInfo(ctx, i) i++ + res := keeper.GetCodeInfo(ctx, i) if res == nil { break }