Raw copy from cosmos/modules (still on v0.37)
This commit is contained in:
73
x/wasm/alias.go
Normal file
73
x/wasm/alias.go
Normal 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
155
x/wasm/client/cli/tx.go
Normal 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
29
x/wasm/genesis.go
Normal 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
99
x/wasm/handler.go
Normal 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
|
||||||
|
}
|
||||||
348
x/wasm/internal/keeper/keeper.go
Normal file
348
x/wasm/internal/keeper/keeper.go
Normal 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))
|
||||||
|
}
|
||||||
176
x/wasm/internal/keeper/keeper_test.go
Normal file
176
x/wasm/internal/keeper/keeper_test.go
Normal 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
|
||||||
|
}
|
||||||
136
x/wasm/internal/keeper/querier.go
Normal file
136
x/wasm/internal/keeper/querier.go
Normal 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
|
||||||
|
}
|
||||||
75
x/wasm/internal/keeper/test_common.go
Normal file
75
x/wasm/internal/keeper/test_common.go
Normal 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
|
||||||
|
}
|
||||||
BIN
x/wasm/internal/keeper/testdata/contract.wasm
vendored
Normal file
BIN
x/wasm/internal/keeper/testdata/contract.wasm
vendored
Normal file
Binary file not shown.
23
x/wasm/internal/types/codec.go
Normal file
23
x/wasm/internal/types/codec.go
Normal 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()
|
||||||
|
}
|
||||||
43
x/wasm/internal/types/errors.go
Normal file
43
x/wasm/internal/types/errors.go
Normal 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))
|
||||||
|
}
|
||||||
48
x/wasm/internal/types/keys.go
Normal file
48
x/wasm/internal/types/keys.go
Normal 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...)
|
||||||
|
}
|
||||||
100
x/wasm/internal/types/msg.go
Normal file
100
x/wasm/internal/types/msg.go
Normal 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}
|
||||||
|
}
|
||||||
80
x/wasm/internal/types/types.go
Normal file
80
x/wasm/internal/types/types.go
Normal 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
136
x/wasm/module.go
Normal 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
352
x/wasm/module_test.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user