mirror of https://github.com/certusone/wasmd.git
Raw copy from cosmos/modules (still on v0.37)
This commit is contained in:
parent
801630d33a
commit
4e8001b01f
|
@ -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
|
||||
)
|
|
@ -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
|
||||
// }
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
@ -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()
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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...)
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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{}
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue