Files
wasmd/x/wasm/keeper/handler_plugin_test.go
Piotr Husiatyński b2fc913398 Keeper tests (#590)
* More keeper tests

x/wasm/keeper tests are extended to test various input validation.
Keeper input is validated before passing to the keeper method when used
within wasmd application. We cannot ensure such validation when this
keeper is used outside of wasmd application. To keep it safe, fully
validate keeper methods input.

hackatom.wasm is loaded into memory during initialization to avoid
reading file in each test separately. Once migrated to go 1.16, embed
package should be used instead.

Run goimport on certain files.

Some comments fixed or removed.

* ensure that creator address is not nil
2021-08-27 15:37:07 +02:00

396 lines
13 KiB
Go

package keeper
import (
"encoding/json"
"testing"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
"github.com/CosmWasm/wasmd/x/wasm/types"
wasmvm "github.com/CosmWasm/wasmvm"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types"
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types"
ibcexported "github.com/cosmos/cosmos-sdk/x/ibc/core/exported"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMessageHandlerChainDispatch(t *testing.T) {
capturingHandler, gotMsgs := wasmtesting.NewCapturingMessageHandler()
alwaysUnknownMsgHandler := &wasmtesting.MockMessageHandler{
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
return nil, nil, types.ErrUnknownMsg
}}
assertNotCalledHandler := &wasmtesting.MockMessageHandler{
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
t.Fatal("not expected to be called")
return
}}
myMsg := wasmvmtypes.CosmosMsg{Custom: []byte(`{}`)}
specs := map[string]struct {
handlers []Messenger
expErr *sdkerrors.Error
expEvents []sdk.Event
}{
"single handler": {
handlers: []Messenger{capturingHandler},
},
"passed to next handler": {
handlers: []Messenger{alwaysUnknownMsgHandler, capturingHandler},
},
"stops iteration when handled": {
handlers: []Messenger{capturingHandler, assertNotCalledHandler},
},
"stops iteration on handler error": {
handlers: []Messenger{&wasmtesting.MockMessageHandler{
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
return nil, nil, types.ErrInvalidMsg
}}, assertNotCalledHandler},
expErr: types.ErrInvalidMsg,
},
"return events when handle": {
handlers: []Messenger{&wasmtesting.MockMessageHandler{
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
_, data, _ = capturingHandler.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg)
return []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))}, data, nil
}},
},
expEvents: []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))},
},
"return error when none can handle": {
handlers: []Messenger{alwaysUnknownMsgHandler},
expErr: types.ErrUnknownMsg,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
*gotMsgs = make([]wasmvmtypes.CosmosMsg, 0)
// when
h := MessageHandlerChain{spec.handlers}
gotEvents, gotData, gotErr := h.DispatchMsg(sdk.Context{}, RandomAccountAddress(t), "anyPort", myMsg)
// then
require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr)
if spec.expErr != nil {
return
}
assert.Equal(t, []wasmvmtypes.CosmosMsg{myMsg}, *gotMsgs)
assert.Equal(t, [][]byte{{1}}, gotData) // {1} is default in capturing handler
assert.Equal(t, spec.expEvents, gotEvents)
})
}
}
func TestSDKMessageHandlerDispatch(t *testing.T) {
myEvent := sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))
const myData = "myData"
myRouterResult := sdk.Result{
Data: []byte(myData),
Events: sdk.Events{myEvent}.ToABCIEvents(),
}
var gotMsg []sdk.Msg
capturingRouteFn := func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
gotMsg = append(gotMsg, msg)
return &myRouterResult, nil
}
myContractAddr := RandomAccountAddress(t)
myContractMessage := wasmvmtypes.CosmosMsg{Custom: []byte("{}")}
specs := map[string]struct {
srcRoute sdk.Route
srcEncoder CustomEncoder
expErr *sdkerrors.Error
expMsgDispatched int
}{
"all good": {
srcRoute: sdk.NewRoute(types.RouterKey, capturingRouteFn),
srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) {
myMsg := types.MsgExecuteContract{
Sender: myContractAddr.String(),
Contract: RandomBech32AccountAddress(t),
Msg: []byte("{}"),
}
return []sdk.Msg{&myMsg}, nil
},
expMsgDispatched: 1,
},
"multiple output msgs": {
srcRoute: sdk.NewRoute(types.RouterKey, capturingRouteFn),
srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) {
first := &types.MsgExecuteContract{
Sender: myContractAddr.String(),
Contract: RandomBech32AccountAddress(t),
Msg: []byte("{}"),
}
second := &types.MsgExecuteContract{
Sender: myContractAddr.String(),
Contract: RandomBech32AccountAddress(t),
Msg: []byte("{}"),
}
return []sdk.Msg{first, second}, nil
},
expMsgDispatched: 2,
},
"invalid sdk message rejected": {
srcRoute: sdk.NewRoute(types.RouterKey, capturingRouteFn),
srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) {
invalidMsg := types.MsgExecuteContract{
Sender: myContractAddr.String(),
Contract: RandomBech32AccountAddress(t),
Msg: []byte("INVALID_JSON"),
}
return []sdk.Msg{&invalidMsg}, nil
},
expErr: types.ErrInvalid,
},
"invalid sender rejected": {
srcRoute: sdk.NewRoute(types.RouterKey, capturingRouteFn),
srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) {
invalidMsg := types.MsgExecuteContract{
Sender: RandomBech32AccountAddress(t),
Contract: RandomBech32AccountAddress(t),
Msg: []byte("{}"),
}
return []sdk.Msg{&invalidMsg}, nil
},
expErr: sdkerrors.ErrUnauthorized,
},
"unroutable message rejected": {
srcRoute: sdk.NewRoute("nothing", capturingRouteFn),
srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) {
myMsg := types.MsgExecuteContract{
Sender: myContractAddr.String(),
Contract: RandomBech32AccountAddress(t),
Msg: []byte("{}"),
}
return []sdk.Msg{&myMsg}, nil
},
expErr: sdkerrors.ErrUnknownRequest,
},
"encoding error passed": {
srcRoute: sdk.NewRoute("nothing", capturingRouteFn),
srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) {
myErr := types.ErrUnpinContractFailed
return nil, myErr
},
expErr: types.ErrUnpinContractFailed,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
gotMsg = make([]sdk.Msg, 0)
router := baseapp.NewRouter()
router.AddRoute(spec.srcRoute)
// when
ctx := sdk.Context{}
h := NewSDKMessageHandler(router, MessageEncoders{Custom: spec.srcEncoder})
gotEvents, gotData, gotErr := h.DispatchMsg(ctx, myContractAddr, "myPort", myContractMessage)
// then
require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr)
if spec.expErr != nil {
require.Len(t, gotMsg, 0)
return
}
assert.Len(t, gotMsg, spec.expMsgDispatched)
for i := 0; i < spec.expMsgDispatched; i++ {
assert.Equal(t, myEvent, gotEvents[i])
assert.Equal(t, []byte(myData), gotData[i])
}
})
}
}
func TestIBCRawPacketHandler(t *testing.T) {
ibcPort := "contractsIBCPort"
var ctx sdk.Context
var capturedPacket ibcexported.PacketI
chanKeeper := &wasmtesting.MockChannelKeeper{
GetNextSequenceSendFn: func(ctx sdk.Context, portID, channelID string) (uint64, bool) {
return 1, true
},
GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channeltypes.Channel, bool) {
return channeltypes.Channel{
Counterparty: channeltypes.NewCounterparty(
"other-port",
"other-channel-1",
)}, true
},
SendPacketFn: func(ctx sdk.Context, channelCap *capabilitytypes.Capability, packet ibcexported.PacketI) error {
capturedPacket = packet
return nil
},
}
capKeeper := &wasmtesting.MockCapabilityKeeper{
GetCapabilityFn: func(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) {
return &capabilitytypes.Capability{}, true
},
}
specs := map[string]struct {
srcMsg wasmvmtypes.SendPacketMsg
chanKeeper types.ChannelKeeper
capKeeper types.CapabilityKeeper
expPacketSent channeltypes.Packet
expErr *sdkerrors.Error
}{
"all good": {
srcMsg: wasmvmtypes.SendPacketMsg{
ChannelID: "channel-1",
Data: []byte("myData"),
Timeout: wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}},
},
chanKeeper: chanKeeper,
capKeeper: capKeeper,
expPacketSent: channeltypes.Packet{
Sequence: 1,
SourcePort: ibcPort,
SourceChannel: "channel-1",
DestinationPort: "other-port",
DestinationChannel: "other-channel-1",
Data: []byte("myData"),
TimeoutHeight: clienttypes.Height{RevisionNumber: 1, RevisionHeight: 2},
},
},
"sequence not found returns error": {
srcMsg: wasmvmtypes.SendPacketMsg{
ChannelID: "channel-1",
Data: []byte("myData"),
Timeout: wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}},
},
chanKeeper: &wasmtesting.MockChannelKeeper{
GetNextSequenceSendFn: func(ctx sdk.Context, portID, channelID string) (uint64, bool) {
return 0, false
}},
expErr: channeltypes.ErrSequenceSendNotFound,
},
"capability not found returns error": {
srcMsg: wasmvmtypes.SendPacketMsg{
ChannelID: "channel-1",
Data: []byte("myData"),
Timeout: wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}},
},
chanKeeper: chanKeeper,
capKeeper: wasmtesting.MockCapabilityKeeper{
GetCapabilityFn: func(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) {
return nil, false
}},
expErr: channeltypes.ErrChannelCapabilityNotFound,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
capturedPacket = nil
// when
h := NewIBCRawPacketHandler(spec.chanKeeper, spec.capKeeper)
data, evts, gotErr := h.DispatchMsg(ctx, RandomAccountAddress(t), ibcPort, wasmvmtypes.CosmosMsg{IBC: &wasmvmtypes.IBCMsg{SendPacket: &spec.srcMsg}})
// then
require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr)
if spec.expErr != nil {
return
}
assert.Nil(t, data)
assert.Nil(t, evts)
assert.Equal(t, spec.expPacketSent, capturedPacket)
})
}
}
func TestBurnCoinMessageHandlerIntegration(t *testing.T) {
// testing via full keeper setup so that we are confident the
// module permissions are set correct and no other handler
// picks the message in the default handler chain
ctx, keepers := CreateDefaultTestInput(t)
k := keepers.WasmKeeper
before, err := keepers.BankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{})
require.NoError(t, err)
example := InstantiateHackatomExampleContract(t, ctx, keepers) // with deposit of 100 stake
specs := map[string]struct {
msg wasmvmtypes.BurnMsg
expErr bool
}{
"all good": {
msg: wasmvmtypes.BurnMsg{
Amount: wasmvmtypes.Coins{{
Denom: "denom",
Amount: "100",
}},
},
},
"not enough funds in contract": {
msg: wasmvmtypes.BurnMsg{
Amount: wasmvmtypes.Coins{{
Denom: "denom",
Amount: "101",
}},
},
expErr: true,
},
"zero amount rejected": {
msg: wasmvmtypes.BurnMsg{
Amount: wasmvmtypes.Coins{{
Denom: "denom",
Amount: "0",
}},
},
expErr: true,
},
"unknown denom - insufficient funds": {
msg: wasmvmtypes.BurnMsg{
Amount: wasmvmtypes.Coins{{
Denom: "unknown",
Amount: "1",
}},
},
expErr: true,
},
}
parentCtx := ctx
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ = parentCtx.CacheContext()
k.wasmVM = &wasmtesting.MockWasmer{ExecuteFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
return &wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{
{Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{Burn: &spec.msg}}, ReplyOn: wasmvmtypes.ReplyNever},
},
}, 0, nil
}}
// when
_, err = k.execute(ctx, example.Contract, example.CreatorAddr, nil, nil)
// then
if spec.expErr {
require.Error(t, err)
return
}
require.NoError(t, err)
// and total supply reduced by burned amount
after, err := keepers.BankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{})
require.NoError(t, err)
diff := before.Supply.Sub(after.Supply)
assert.Equal(t, sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(100))), diff)
})
}
// test cases:
// not enough money to burn
}