Raw copy from cosmos/modules (still on v0.37)

This commit is contained in:
Ethan Frey 2019-11-22 16:57:10 +01:00
parent 801630d33a
commit 4e8001b01f
16 changed files with 1873 additions and 0 deletions

73
x/wasm/alias.go Normal file
View File

@ -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
)

155
x/wasm/client/cli/tx.go Normal file
View File

@ -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
// }

29
x/wasm/genesis.go Normal file
View File

@ -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
}

99
x/wasm/handler.go Normal file
View File

@ -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
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

Binary file not shown.

View File

@ -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()
}

View File

@ -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))
}

View File

@ -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...)
}

View File

@ -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}
}

View File

@ -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,
}
}

136
x/wasm/module.go Normal file
View File

@ -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{}
}

352
x/wasm/module_test.go Normal file
View File

@ -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
}