From 78d5581040fc6ad0bb7d1790eabed848e73c3a3d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 10 Mar 2021 09:46:12 +0100 Subject: [PATCH] Submsg and replies (#441) * Add dispatchSubmessages and reply to Keeper * Update all mock types * Dispatch submessages in all entry points * Rename mask -> reflect in all tests (that was cosmwasm 0.8...) * Basic submessage dispatch test; * Simplify messanger interface again * Start table tests * Added table tests * Debuging handling out of gas and panics * Properly handle gas limits and out of gas panics * Test parsing return values from WasmMsg::Instantiate * PR feedback * Add test to trigger 0 len data panic * Safely handle 0 sdk msg submsg responses * Charge gas on reply --- x/wasm/internal/keeper/handler_plugin.go | 51 +- x/wasm/internal/keeper/keeper.go | 209 +++++++- x/wasm/internal/keeper/reflect_test.go | 214 +++++---- x/wasm/internal/keeper/relay.go | 15 +- x/wasm/internal/keeper/relay_test.go | 30 +- x/wasm/internal/keeper/staking_test.go | 16 +- x/wasm/internal/keeper/submsg_test.go | 452 ++++++++++++++++++ .../internal/keeper/wasmtesting/messenger.go | 23 +- .../keeper/wasmtesting/mock_engine.go | 8 + x/wasm/internal/types/wasmer_engine.go | 12 + 10 files changed, 839 insertions(+), 191 deletions(-) create mode 100644 x/wasm/internal/keeper/submsg_test.go diff --git a/x/wasm/internal/keeper/handler_plugin.go b/x/wasm/internal/keeper/handler_plugin.go index 02fb42ad..e263e2fa 100644 --- a/x/wasm/internal/keeper/handler_plugin.go +++ b/x/wasm/internal/keeper/handler_plugin.go @@ -320,50 +320,49 @@ func convertWasmIBCTimeoutTimestampToCosmosTimestamp(timestamp *uint64) uint64 { return *timestamp } -func (h DefaultMessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - for _, msg := range msgs { - sdkMsgs, err := h.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) - if err != nil { - return err - } - for _, sdkMsg := range sdkMsgs { - if err := h.handleSdkMessage(ctx, contractAddr, sdkMsg); err != nil { - return err - } - } +func (h DefaultMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + sdkMsgs, err := h.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) + if err != nil { + return nil, nil, err } - return nil + for _, sdkMsg := range sdkMsgs { + res, err := h.handleSdkMessage(ctx, contractAddr, sdkMsg) + if err != nil { + return nil, nil, err + } + // append data + data = append(data, res.Data) + // append events + sdkEvents := make([]sdk.Event, len(res.Events)) + for i := range res.Events { + sdkEvents[i] = sdk.Event(res.Events[i]) + } + events = append(events, sdkEvents...) + } + return } -func (h DefaultMessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) error { +func (h DefaultMessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) { if err := msg.ValidateBasic(); err != nil { - return err + return nil, err } // make sure this account can send it for _, acct := range msg.GetSigners() { if !acct.Equals(contractAddr) { - return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") } } // find the handler and execute it handler := h.router.Route(ctx, msg.Route()) if handler == nil { - return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, msg.Route()) + return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, msg.Route()) } res, err := handler(ctx, msg) if err != nil { - return err + return nil, err } - - events := make(sdk.Events, len(res.Events)) - for i := range res.Events { - events[i] = sdk.Event(res.Events[i]) - } - // redispatch all events, (type sdk.EventTypeMessage will be filtered out in the handler) - ctx.EventManager().EmitEvents(events) - - return nil + return res, nil } func convertWasmCoinsToSdkCoins(coins []wasmvmtypes.Coin) (sdk.Coins, error) { diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index e212b186..ad37f1b8 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "fmt" + abci "github.com/tendermint/tendermint/abci/types" "path/filepath" "github.com/CosmWasm/wasmd/x/wasm/internal/types" @@ -52,7 +53,7 @@ type Option interface { } type messenger interface { - Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error + DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) } // Keeper will have a reference to Wasmer with it's own data directory. @@ -220,7 +221,7 @@ func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins, authZ AuthorizationPolicy) (sdk.AccAddress, []byte, error) { if !k.IsPinnedCode(ctx, codeID) { - ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: init") + ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: instantiate") } // create contract address @@ -301,13 +302,14 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A contractInfo.IBCPortID = ibcPort } + // store contract before dispatch so that contract could be called back k.storeContractInfo(ctx, contractAddress, &contractInfo) k.appendToContractHistory(ctx, contractAddress, contractInfo.InitialHistory(initMsg)) - // then dispatch so that contract could be called back - err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages) + // dispatch submessages then messages + err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages) if err != nil { - return nil, nil, err + return nil, nil, sdkerrors.Wrap(err, "dispatch") } return contractAddress, res.Data, nil @@ -352,9 +354,10 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller events := types.ParseEvents(res.Attributes, contractAddress) ctx.EventManager().EmitEvents(events) - err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages) + // dispatch submessages then messages + err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(err, "dispatch") } return &sdk.Result{ @@ -426,8 +429,9 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller k.appendToContractHistory(ctx, contractAddress, historyEntry) k.storeContractInfo(ctx, contractAddress, contractInfo) - // then dispatch - if err := k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages); err != nil { + // dispatch submessages then messages + err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages) + if err != nil { return nil, sdkerrors.Wrap(err, "dispatch") } @@ -464,7 +468,50 @@ func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte events := types.ParseEvents(res.Attributes, contractAddress) ctx.EventManager().EmitEvents(events) - err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages) + // dispatch submessages then messages + err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages) + if err != nil { + return nil, sdkerrors.Wrap(err, "dispatch") + } + + return &sdk.Result{ + Data: res.Data, + }, nil +} + +// reply is only called from keeper internal functions (dispatchSubmessages) after processing the submessage +// it +func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) { + contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress) + if err != nil { + return nil, err + } + + // current thought is to charge gas like a fresh run, we can revisit whether to give it a discount later + if !k.IsPinnedCode(ctx, contractInfo.CodeID) { + ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: reply") + } + + env := types.NewEnv(ctx, contractAddress) + + // prepare querier + querier := QueryHandler{ + Ctx: ctx, + Plugins: k.queryPlugins, + } + gas := gasForContract(ctx) + res, gasUsed, execErr := k.wasmer.Reply(codeInfo.CodeHash, env, reply, prefixStore, cosmwasmAPI, querier, gasMeter(ctx), gas) + consumeGas(ctx, gasUsed) + if execErr != nil { + return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) + } + + // emit all events from this contract itself + events := types.ParseEvents(res.Attributes, contractAddress) + ctx.EventManager().EmitEvents(events) + + // dispatch submessages then messages + err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages) if err != nil { return nil, sdkerrors.Wrap(err, "dispatch") } @@ -678,15 +725,6 @@ func (k Keeper) GetByteCode(ctx sdk.Context, codeID uint64) ([]byte, error) { return k.wasmer.GetCode(codeInfo.CodeHash) } -func (k Keeper) dispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error { - for _, msg := range msgs { - if err := k.messenger.Dispatch(ctx, contractAddr, ibcPort, msg); err != nil { - return err - } - } - return nil -} - // PinCode pins the wasm contract in wasmvm cache func (k Keeper) PinCode(ctx sdk.Context, codeID uint64) error { codeInfo := k.GetCodeInfo(ctx, codeID) @@ -740,6 +778,139 @@ func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error { return nil } +func (k Keeper) dispatchAll(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, subMsgs []wasmvmtypes.SubMsg, msgs []wasmvmtypes.CosmosMsg) error { + // first dispatch all submessages (and the replies). + err := k.dispatchSubmessages(ctx, contractAddr, ibcPort, subMsgs) + if err != nil { + return err + } + // then dispatch all the normal messages + return k.dispatchMessages(ctx, contractAddr, ibcPort, msgs) +} + +func (k Keeper) dispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error { + for _, msg := range msgs { + events, _, err := k.messenger.DispatchMsg(ctx, contractAddr, ibcPort, msg) + if err != nil { + return err + } + // redispatch all events, (type sdk.EventTypeMessage will be filtered out in the handler) + ctx.EventManager().EmitEvents(events) + } + return nil +} + +func (k Keeper) dispatchMsgWithGasLimit(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msg wasmvmtypes.CosmosMsg, gasLimit uint64) (events []sdk.Event, data [][]byte, err error) { + limitedMeter := sdk.NewGasMeter(gasLimit) + subCtx := ctx.WithGasMeter(limitedMeter) + + // catch out of gas panic and just charge the entire gas limit + defer func() { + if r := recover(); r != nil { + // if it's not an OutOfGas error, raise it again + if _, ok := r.(sdk.ErrorOutOfGas); !ok { + // log it to get the original stack trace somewhere (as panic(r) keeps message but stacktrace to here + k.Logger(ctx).Info("SubMsg rethrowing panic: %#v", r) + panic(r) + } + ctx.GasMeter().ConsumeGas(gasLimit, "Sub-Message OutOfGas panic") + err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "SubMsg hit gas limit") + } + }() + events, data, err = k.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg) + + // make sure we charge the parent what was spent + spent := subCtx.GasMeter().GasConsumed() + ctx.GasMeter().ConsumeGas(spent, "From limited Sub-Message") + + return events, data, err +} + +// dispatchSubmessages builds a sandbox to execute these messages and returns the execution result to the contract +// that dispatched them, both on success as well as failure +func (k Keeper) dispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) error { + for _, msg := range msgs { + // first, we build a sub-context which we can use inside the submessages + subCtx, commit := ctx.CacheContext() + + // check how much gas left locally, optionally wrap the gas meter + gasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumed() + limitGas := msg.GasLimit != nil && (*msg.GasLimit < gasRemaining) + + var err error + var events []sdk.Event + var data [][]byte + if limitGas { + events, data, err = k.dispatchMsgWithGasLimit(subCtx, contractAddr, ibcPort, msg.Msg, *msg.GasLimit) + } else { + events, data, err = k.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg.Msg) + } + + // if it succeeds, commit state changes from submessage, and pass on events to Event Manager + if err == nil { + commit() + ctx.EventManager().EmitEvents(events) + } + // on failure, revert state from sandbox, and ignore events (just skip doing the above) + + var result wasmvmtypes.SubcallResult + if err == nil { + // just take the first one for now if there are multiple sub-sdk messages + // and safely return nothing if no data + var responseData []byte + if len(data) > 0 { + responseData = data[0] + } + result = wasmvmtypes.SubcallResult{ + Ok: &wasmvmtypes.SubcallResponse{ + Events: sdkEventsToWasmVmEvents(events), + Data: responseData, + }, + } + } else { + result = wasmvmtypes.SubcallResult{ + Err: err.Error(), + } + } + + // now handle the reply, we use the parent context, and abort on error + reply := wasmvmtypes.Reply{ + ID: msg.ID, + Result: result, + } + + // we can ignore any result returned as there is nothing to do with the data + // and the events are already in the ctx.EventManager() + _, err = k.reply(ctx, contractAddr, reply) + if err != nil { + return err + } + } + return nil +} + +func sdkEventsToWasmVmEvents(events []sdk.Event) []wasmvmtypes.Event { + res := make([]wasmvmtypes.Event, len(events)) + for i, ev := range events { + res[i] = wasmvmtypes.Event{ + Type: ev.Type, + Attributes: sdkAttributesToWasmVmAttributes(ev.Attributes), + } + } + return res +} + +func sdkAttributesToWasmVmAttributes(attrs []abci.EventAttribute) []wasmvmtypes.EventAttribute { + res := make([]wasmvmtypes.EventAttribute, len(attrs)) + for i, attr := range attrs { + res[i] = wasmvmtypes.EventAttribute{ + Key: string(attr.Key), + Value: string(attr.Value), + } + } + return res +} + func gasForContract(ctx sdk.Context) uint64 { meter := ctx.GasMeter() if meter.IsOutOfGas() { diff --git a/x/wasm/internal/keeper/reflect_test.go b/x/wasm/internal/keeper/reflect_test.go index c7635358..1cf359d0 100644 --- a/x/wasm/internal/keeper/reflect_test.go +++ b/x/wasm/internal/keeper/reflect_test.go @@ -20,12 +20,13 @@ import ( "github.com/stretchr/testify/require" ) -// MaskInitMsg is {} +// ReflectInitMsg is {} -// MaskHandleMsg is used to encode handle messages -type MaskHandleMsg struct { - Reflect *reflectPayload `json:"reflect_msg,omitempty"` - Change *ownerPayload `json:"change_owner,omitempty"` +// ReflectHandleMsg is used to encode handle messages +type ReflectHandleMsg struct { + Reflect *reflectPayload `json:"reflect_msg,omitempty"` + ReflectSubCall *reflectSubPayload `json:"reflect_sub_call,omitempty"` + Change *ownerPayload `json:"change_owner,omitempty"` } type ownerPayload struct { @@ -36,11 +37,16 @@ type reflectPayload struct { Msgs []wasmvmtypes.CosmosMsg `json:"msgs"` } -// MaskQueryMsg is used to encode query messages -type MaskQueryMsg struct { - Owner *struct{} `json:"owner,omitempty"` - Capitalized *Text `json:"capitalized,omitempty"` - Chain *ChainQuery `json:"chain,omitempty"` +type reflectSubPayload struct { + Msgs []wasmvmtypes.SubMsg `json:"msgs"` +} + +// ReflectQueryMsg is used to encode query messages +type ReflectQueryMsg struct { + Owner *struct{} `json:"owner,omitempty"` + Capitalized *Text `json:"capitalized,omitempty"` + Chain *ChainQuery `json:"chain,omitempty"` + SubCallResult *SubCall `json:"sub_call_result,omitempty"` } type ChainQuery struct { @@ -51,6 +57,10 @@ type Text struct { Text string `json:"text"` } +type SubCall struct { + ID uint64 `json:"id"` +} + type OwnerResponse struct { Owner string `json:"owner,omitempty"` } @@ -59,7 +69,7 @@ type ChainResponse struct { Data []byte `json:"data,omitempty"` } -func buildMaskQuery(t *testing.T, query *MaskQueryMsg) []byte { +func buildReflectQuery(t *testing.T, query *ReflectQueryMsg) []byte { bz, err := json.Marshal(query) require.NoError(t, err) return bz @@ -70,23 +80,23 @@ func mustParse(t *testing.T, data []byte, res interface{}) { require.NoError(t, err) } -const MaskFeatures = "staking,mask,stargate" +const ReflectFeatures = "staking,mask,stargate" -func TestMaskReflectContractSend(t *testing.T) { +func TestReflectContractSend(t *testing.T) { cdc := MakeTestCodec(t) - ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), nil) + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), nil) accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit) _, _, bob := keyPubAddr() - // upload mask code - maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + // upload reflect code + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) - maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + reflectID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) require.NoError(t, err) - require.Equal(t, uint64(1), maskID) + require.Equal(t, uint64(1), reflectID) // upload hackatom escrow code escrowCode, err := ioutil.ReadFile("./testdata/hackatom.wasm") @@ -96,14 +106,14 @@ func TestMaskReflectContractSend(t *testing.T) { require.Equal(t, uint64(2), escrowID) // creator instantiates a contract and gives it tokens - maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - maskAddr, _, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart) + reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) + reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) require.NoError(t, err) - require.NotEmpty(t, maskAddr) + require.NotEmpty(t, reflectAddr) // now we set contract as verifier of an escrow initMsg := HackatomExampleInitMsg{ - Verifier: maskAddr, + Verifier: reflectAddr, Beneficiary: bob, } initMsgBz, err := json.Marshal(initMsg) @@ -115,13 +125,13 @@ func TestMaskReflectContractSend(t *testing.T) { // let's make sure all balances make sense checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // 100k - 40k - 25k - checkAccount(t, ctx, accKeeper, bankKeeper, maskAddr, maskStart) + checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, reflectStart) checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, escrowStart) checkAccount(t, ctx, accKeeper, bankKeeper, bob, nil) - // now for the trick.... we reflect a message through the mask to call the escrow + // now for the trick.... we reflect a message through the reflect to call the escrow // we also send an additional 14k tokens there. - // this should reduce the mask balance by 14k (to 26k) + // this should reduce the reflect balance by 14k (to 26k) // this 14k is added to the escrow, then the entire balance is sent to bob (total: 39k) approveMsg := []byte(`{"release":{}}`) msgs := []wasmvmtypes.CosmosMsg{{ @@ -136,27 +146,27 @@ func TestMaskReflectContractSend(t *testing.T) { }, }, }} - reflectSend := MaskHandleMsg{ + reflectSend := ReflectHandleMsg{ Reflect: &reflectPayload{ Msgs: msgs, }, } reflectSendBz, err := json.Marshal(reflectSend) require.NoError(t, err) - _, err = keeper.Execute(ctx, maskAddr, creator, reflectSendBz, nil) + _, err = keeper.Execute(ctx, reflectAddr, creator, reflectSendBz, nil) require.NoError(t, err) // did this work??? - checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // same as before - checkAccount(t, ctx, accKeeper, bankKeeper, maskAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 26000))) // 40k - 14k (from send) - checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, sdk.Coins{}) // emptied reserved - checkAccount(t, ctx, accKeeper, bankKeeper, bob, sdk.NewCoins(sdk.NewInt64Coin("denom", 39000))) // all escrow of 25k + 14k + checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // same as before + checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 26000))) // 40k - 14k (from send) + checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, sdk.Coins{}) // emptied reserved + checkAccount(t, ctx, accKeeper, bankKeeper, bob, sdk.NewCoins(sdk.NewInt64Coin("denom", 39000))) // all escrow of 25k + 14k } -func TestMaskReflectCustomMsg(t *testing.T) { +func TestReflectCustomMsg(t *testing.T) { cdc := MakeTestCodec(t) - ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), maskPlugins()) + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), reflectPlugins()) accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) @@ -165,20 +175,20 @@ func TestMaskReflectCustomMsg(t *testing.T) { _, _, fred := keyPubAddr() // upload code - maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) - codeID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) // creator instantiates a contract and gives it tokens contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart) + contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) require.NoError(t, err) require.NotEmpty(t, contractAddr) // set owner to bob - transfer := MaskHandleMsg{ + transfer := ReflectHandleMsg{ Change: &ownerPayload{ Owner: bob, }, @@ -205,7 +215,7 @@ func TestMaskReflectCustomMsg(t *testing.T) { }, }, }} - reflectSend := MaskHandleMsg{ + reflectSend := ReflectHandleMsg{ Reflect: &reflectPayload{ Msgs: msgs, }, @@ -227,9 +237,9 @@ func TestMaskReflectCustomMsg(t *testing.T) { ToAddress: fred.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("denom", 23000)), } - opaque, err := toMaskRawMsg(cdc, sdkSendMsg) + opaque, err := toReflectRawMsg(cdc, sdkSendMsg) require.NoError(t, err) - reflectOpaque := MaskHandleMsg{ + reflectOpaque := ReflectHandleMsg{ Reflect: &reflectPayload{ Msgs: []wasmvmtypes.CosmosMsg{opaque}, }, @@ -249,27 +259,27 @@ func TestMaskReflectCustomMsg(t *testing.T) { func TestMaskReflectCustomQuery(t *testing.T) { cdc := MakeTestCodec(t) - ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), maskPlugins()) + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), reflectPlugins()) accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit) // upload code - maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) - codeID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) // creator instantiates a contract and gives it tokens contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart) + contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) require.NoError(t, err) require.NotEmpty(t, contractAddr) // let's perform a normal query of state - ownerQuery := MaskQueryMsg{ + ownerQuery := ReflectQueryMsg{ Owner: &struct{}{}, } ownerQueryBz, err := json.Marshal(ownerQuery) @@ -282,7 +292,7 @@ func TestMaskReflectCustomQuery(t *testing.T) { assert.Equal(t, res.Owner, creator.String()) // and now making use of the custom querier callbacks - customQuery := MaskQueryMsg{ + customQuery := ReflectQueryMsg{ Capitalized: &Text{ Text: "all Caps noW", }, @@ -299,7 +309,7 @@ func TestMaskReflectCustomQuery(t *testing.T) { func TestReflectStargateQuery(t *testing.T) { cdc := MakeTestCodec(t) - ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), maskPlugins()) + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(cdc), reflectPlugins()) accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000)) @@ -308,14 +318,14 @@ func TestReflectStargateQuery(t *testing.T) { creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, funds) // upload code - maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) - codeID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) // creator instantiates a contract and gives it tokens - contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart) + contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) require.NoError(t, err) require.NotEmpty(t, contractAddr) @@ -327,7 +337,7 @@ func TestReflectStargateQuery(t *testing.T) { }, }, } - simpleQueryBz, err := json.Marshal(MaskQueryMsg{ + simpleQueryBz, err := json.Marshal(ReflectQueryMsg{ Chain: &ChainQuery{Request: &bankQuery}, }) require.NoError(t, err) @@ -352,7 +362,7 @@ func TestReflectStargateQuery(t *testing.T) { Data: protoQueryBin, }, } - protoQueryBz, err := json.Marshal(MaskQueryMsg{ + protoQueryBz, err := json.Marshal(ReflectQueryMsg{ Chain: &ChainQuery{Request: &protoRequest}, }) require.NoError(t, err) @@ -370,33 +380,33 @@ func TestReflectStargateQuery(t *testing.T) { assert.Equal(t, expectedBalance, protoResult.Balances) } -type maskState struct { +type reflectState struct { Owner []byte `json:"owner"` } func TestMaskReflectWasmQueries(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(MakeTestCodec(t)), nil) + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(MakeTestCodec(t)), nil) accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := createFakeFundedAccount(t, ctx, accKeeper, keepers.BankKeeper, deposit) - // upload mask code - maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + // upload reflect code + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) - maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + reflectID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) require.NoError(t, err) - require.Equal(t, uint64(1), maskID) + require.Equal(t, uint64(1), reflectID) // creator instantiates a contract and gives it tokens - maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - maskAddr, _, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart) + reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) + reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) require.NoError(t, err) - require.NotEmpty(t, maskAddr) + require.NotEmpty(t, reflectAddr) - // for control, let's make some queries directly on the mask - ownerQuery := buildMaskQuery(t, &MaskQueryMsg{Owner: &struct{}{}}) - res, err := keeper.QuerySmart(ctx, maskAddr, ownerQuery) + // for control, let's make some queries directly on the reflect + ownerQuery := buildReflectQuery(t, &ReflectQueryMsg{Owner: &struct{}{}}) + res, err := keeper.QuerySmart(ctx, reflectAddr, ownerQuery) require.NoError(t, err) var ownerRes OwnerResponse mustParse(t, res, &ownerRes) @@ -404,20 +414,20 @@ func TestMaskReflectWasmQueries(t *testing.T) { // and a raw query: cosmwasm_storage::Singleton uses 2 byte big-endian length-prefixed to store data configKey := append([]byte{0, 6}, []byte("config")...) - raw := keeper.QueryRaw(ctx, maskAddr, configKey) - var stateRes maskState + raw := keeper.QueryRaw(ctx, reflectAddr, configKey) + var stateRes reflectState mustParse(t, raw, &stateRes) require.Equal(t, stateRes.Owner, []byte(creator)) // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result - reflectOwnerQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ + reflectOwnerQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Smart: &wasmvmtypes.SmartQuery{ - ContractAddr: maskAddr.String(), + ContractAddr: reflectAddr.String(), Msg: ownerQuery, }, }}}} - reflectOwnerBin := buildMaskQuery(t, &reflectOwnerQuery) - res, err = keeper.QuerySmart(ctx, maskAddr, reflectOwnerBin) + reflectOwnerBin := buildReflectQuery(t, &reflectOwnerQuery) + res, err = keeper.QuerySmart(ctx, reflectAddr, reflectOwnerBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response var reflectRes ChainResponse @@ -427,58 +437,58 @@ func TestMaskReflectWasmQueries(t *testing.T) { require.Equal(t, reflectOwnerRes.Owner, creator.String()) // and with queryRaw - reflectStateQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ + reflectStateQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Raw: &wasmvmtypes.RawQuery{ - ContractAddr: maskAddr.String(), + ContractAddr: reflectAddr.String(), Key: configKey, }, }}}} - reflectStateBin := buildMaskQuery(t, &reflectStateQuery) - res, err = keeper.QuerySmart(ctx, maskAddr, reflectStateBin) + reflectStateBin := buildReflectQuery(t, &reflectStateQuery) + res, err = keeper.QuerySmart(ctx, reflectAddr, reflectStateBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response var reflectRawRes ChainResponse mustParse(t, res, &reflectRawRes) // now, with the raw data, we can parse it into state - var reflectStateRes maskState + var reflectStateRes reflectState mustParse(t, reflectRawRes.Data, &reflectStateRes) require.Equal(t, reflectStateRes.Owner, []byte(creator)) } func TestWasmRawQueryWithNil(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(MakeTestCodec(t)), nil) + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, reflectEncoders(MakeTestCodec(t)), nil) accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := createFakeFundedAccount(t, ctx, accKeeper, keepers.BankKeeper, deposit) - // upload mask code - maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + // upload reflect code + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) - maskID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + reflectID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) require.NoError(t, err) - require.Equal(t, uint64(1), maskID) + require.Equal(t, uint64(1), reflectID) // creator instantiates a contract and gives it tokens - maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - maskAddr, _, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart) + reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) + reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) require.NoError(t, err) - require.NotEmpty(t, maskAddr) + require.NotEmpty(t, reflectAddr) // control: query directly missingKey := []byte{0, 1, 2, 3, 4} - raw := keeper.QueryRaw(ctx, maskAddr, missingKey) + raw := keeper.QueryRaw(ctx, reflectAddr, missingKey) require.Nil(t, raw) // and with queryRaw - reflectQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ + reflectQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Raw: &wasmvmtypes.RawQuery{ - ContractAddr: maskAddr.String(), + ContractAddr: reflectAddr.String(), Key: missingKey, }, }}}} - reflectStateBin := buildMaskQuery(t, &reflectQuery) - res, err := keeper.QuerySmart(ctx, maskAddr, reflectStateBin) + reflectStateBin := buildReflectQuery(t, &reflectQuery) + res, err := keeper.QuerySmart(ctx, reflectAddr, reflectStateBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response @@ -507,14 +517,14 @@ func checkAccount(t *testing.T, ctx sdk.Context, accKeeper authkeeper.AccountKee /**** Code to support custom messages *****/ -type maskCustomMsg struct { +type reflectCustomMsg struct { Debug string `json:"debug,omitempty"` Raw []byte `json:"raw,omitempty"` } -// toMaskRawMsg encodes an sdk msg using any type with json encoding. +// toReflectRawMsg encodes an sdk msg using any type with json encoding. // Then wraps it as an opaque message -func toMaskRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, error) { +func toReflectRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, error) { any, err := codectypes.NewAnyWithValue(msg) if err != nil { return wasmvmtypes.CosmosMsg{}, err @@ -523,7 +533,7 @@ func toMaskRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, erro if err != nil { return wasmvmtypes.CosmosMsg{}, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } - customMsg, err := json.Marshal(maskCustomMsg{ + customMsg, err := json.Marshal(reflectCustomMsg{ Raw: rawBz, }) res := wasmvmtypes.CosmosMsg{ @@ -532,18 +542,18 @@ func toMaskRawMsg(cdc codec.Marshaler, msg sdk.Msg) (wasmvmtypes.CosmosMsg, erro return res, nil } -// maskEncoders needs to be registered in test setup to handle custom message callbacks -func maskEncoders(cdc codec.Marshaler) *MessageEncoders { +// reflectEncoders needs to be registered in test setup to handle custom message callbacks +func reflectEncoders(cdc codec.Marshaler) *MessageEncoders { return &MessageEncoders{ - Custom: fromMaskRawMsg(cdc), + Custom: fromReflectRawMsg(cdc), } } -// fromMaskRawMsg decodes msg.Data to an sdk.Msg using proto Any and json encoding. +// fromReflectRawMsg decodes msg.Data to an sdk.Msg using proto Any and json encoding. // this needs to be registered on the Encoders -func fromMaskRawMsg(cdc codec.Marshaler) CustomEncoder { +func fromReflectRawMsg(cdc codec.Marshaler) CustomEncoder { return func(_sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - var custom maskCustomMsg + var custom reflectCustomMsg err := json.Unmarshal(msg, &custom) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) @@ -566,7 +576,7 @@ func fromMaskRawMsg(cdc codec.Marshaler) CustomEncoder { } } -type maskCustomQuery struct { +type reflectCustomQuery struct { Ping *struct{} `json:"ping,omitempty"` Capitalized *Text `json:"capitalized,omitempty"` } @@ -589,15 +599,15 @@ type chainResponse struct { Data []byte `json:"data"` } -// maskPlugins needs to be registered in test setup to handle custom query callbacks -func maskPlugins() *QueryPlugins { +// reflectPlugins needs to be registered in test setup to handle custom query callbacks +func reflectPlugins() *QueryPlugins { return &QueryPlugins{ Custom: performCustomQuery, } } func performCustomQuery(_ sdk.Context, request json.RawMessage) ([]byte, error) { - var custom maskCustomQuery + var custom reflectCustomQuery err := json.Unmarshal(request, &custom) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) diff --git a/x/wasm/internal/keeper/relay.go b/x/wasm/internal/keeper/relay.go index 3db1194f..14ae1506 100644 --- a/x/wasm/internal/keeper/relay.go +++ b/x/wasm/internal/keeper/relay.go @@ -66,7 +66,8 @@ func (k Keeper) OnConnectChannel( events := types.ParseEvents(res.Attributes, contractAddr) ctx.EventManager().EmitEvents(events) - if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil { + // TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged + if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil { return err } return nil @@ -102,7 +103,8 @@ func (k Keeper) OnCloseChannel( events := types.ParseEvents(res.Attributes, contractAddr) ctx.EventManager().EmitEvents(events) - if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil { + // TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged + if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil { return err } return nil @@ -138,7 +140,8 @@ func (k Keeper) OnRecvPacket( events := types.ParseEvents(res.Attributes, contractAddr) ctx.EventManager().EmitEvents(events) - if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil { + // TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged + if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil { return nil, err } return res.Acknowledgement, nil @@ -175,7 +178,8 @@ func (k Keeper) OnAckPacket( events := types.ParseEvents(res.Attributes, contractAddr) ctx.EventManager().EmitEvents(events) - if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil { + // TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged + if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil { return err } return nil @@ -208,7 +212,8 @@ func (k Keeper) OnTimeoutPacket( events := types.ParseEvents(res.Attributes, contractAddr) ctx.EventManager().EmitEvents(events) - if err := k.messenger.Dispatch(ctx, contractAddr, contractInfo.IBCPortID, res.Messages...); err != nil { + // TODO: add submessages support here (and everywhere else) once https://github.com/CosmWasm/cosmwasm/issues/822 is merged + if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, nil, res.Messages); err != nil { return err } return nil diff --git a/x/wasm/internal/keeper/relay_test.go b/x/wasm/internal/keeper/relay_test.go index 9e884bbf..cb92eb68 100644 --- a/x/wasm/internal/keeper/relay_test.go +++ b/x/wasm/internal/keeper/relay_test.go @@ -125,11 +125,7 @@ func TestOnConnectChannel(t *testing.T) { Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, - overwriteMessenger: &wasmtesting.MockMessageHandler{ - DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - return errors.New("test, ignore") - }, - }, + overwriteMessenger: wasmtesting.NewErroringMessageHandler(), expErr: true, expContractEventAttrs: 1, }, @@ -239,11 +235,7 @@ func TestOnCloseChannel(t *testing.T) { Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, - overwriteMessenger: &wasmtesting.MockMessageHandler{ - DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - return errors.New("test, ignore") - }, - }, + overwriteMessenger: wasmtesting.NewErroringMessageHandler(), expErr: true, expContractEventAttrs: 1, }, @@ -364,11 +356,7 @@ func TestOnRecvPacket(t *testing.T) { Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, - overwriteMessenger: &wasmtesting.MockMessageHandler{ - DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - return errors.New("test, ignore") - }, - }, + overwriteMessenger: wasmtesting.NewErroringMessageHandler(), expErr: true, expContractEventAttrs: 1, }, @@ -482,11 +470,7 @@ func TestOnAckPacket(t *testing.T) { Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, - overwriteMessenger: &wasmtesting.MockMessageHandler{ - DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - return errors.New("test, ignore") - }, - }, + overwriteMessenger: wasmtesting.NewErroringMessageHandler(), expErr: true, expContractEventAttrs: 1, }, @@ -597,11 +581,7 @@ func TestOnTimeoutPacket(t *testing.T) { Messages: []wasmvmtypes.CosmosMsg{{Bank: &wasmvmtypes.BankMsg{}}, {Custom: json.RawMessage(`{"foo":"bar"}`)}}, Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, }, - overwriteMessenger: &wasmtesting.MockMessageHandler{ - DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - return errors.New("test, ignore") - }, - }, + overwriteMessenger: wasmtesting.NewErroringMessageHandler(), expErr: true, expContractEventAttrs: 1, }, diff --git a/x/wasm/internal/keeper/staking_test.go b/x/wasm/internal/keeper/staking_test.go index 6b319394..8fc3b64b 100644 --- a/x/wasm/internal/keeper/staking_test.go +++ b/x/wasm/internal/keeper/staking_test.go @@ -453,10 +453,10 @@ func TestQueryStakingInfo(t *testing.T) { // STEP 3: now, let's reflect some queries. // let's get the bonded denom - reflectBondedQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectBondedQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ BondedDenom: &struct{}{}, }}}} - reflectBondedBin := buildMaskQuery(t, &reflectBondedQuery) + reflectBondedBin := buildReflectQuery(t, &reflectBondedQuery) res, err := keeper.QuerySmart(ctx, maskAddr, reflectBondedBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response @@ -467,10 +467,10 @@ func TestQueryStakingInfo(t *testing.T) { assert.Equal(t, "stake", bondedRes.Denom) // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result - reflectValidatorsQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectValidatorsQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ Validators: &wasmvmtypes.ValidatorsQuery{}, }}}} - reflectValidatorsBin := buildMaskQuery(t, &reflectValidatorsQuery) + reflectValidatorsBin := buildReflectQuery(t, &reflectValidatorsQuery) res, err = keeper.QuerySmart(ctx, maskAddr, reflectValidatorsBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response @@ -486,12 +486,12 @@ func TestQueryStakingInfo(t *testing.T) { require.Contains(t, valInfo.MaxChangeRate, "0.010") // test to get all my delegations - reflectAllDelegationsQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectAllDelegationsQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ AllDelegations: &wasmvmtypes.AllDelegationsQuery{ Delegator: contractAddr.String(), }, }}}} - reflectAllDelegationsBin := buildMaskQuery(t, &reflectAllDelegationsQuery) + reflectAllDelegationsBin := buildReflectQuery(t, &reflectAllDelegationsQuery) res, err = keeper.QuerySmart(ctx, maskAddr, reflectAllDelegationsBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response @@ -509,13 +509,13 @@ func TestQueryStakingInfo(t *testing.T) { require.Equal(t, funds[0].Amount.String(), delInfo.Amount.Amount) // test to get one delegations - reflectDelegationQuery := MaskQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectDelegationQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ Delegation: &wasmvmtypes.DelegationQuery{ Validator: valAddr.String(), Delegator: contractAddr.String(), }, }}}} - reflectDelegationBin := buildMaskQuery(t, &reflectDelegationQuery) + reflectDelegationBin := buildReflectQuery(t, &reflectDelegationQuery) res, err = keeper.QuerySmart(ctx, maskAddr, reflectDelegationBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response diff --git a/x/wasm/internal/keeper/submsg_test.go b/x/wasm/internal/keeper/submsg_test.go new file mode 100644 index 00000000..c287e5cf --- /dev/null +++ b/x/wasm/internal/keeper/submsg_test.go @@ -0,0 +1,452 @@ +package keeper + +import ( + "encoding/json" + "fmt" + "github.com/stretchr/testify/assert" + "io/ioutil" + "strconv" + "testing" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +// test handing of submessages, very closely related to the reflect_test + +// Try a simple send, no gas limit to for a sanity check before trying table tests +func TestDispatchSubMsgSuccessCase(t *testing.T) { + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, nil, nil) + accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) + + creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit) + creatorBalance := deposit.Sub(contractStart) + _, _, fred := keyPubAddr() + + // upload code + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + require.NoError(t, err) + codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) + require.NoError(t, err) + require.Equal(t, uint64(1), codeID) + + // creator instantiates a contract and gives it tokens + contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) + require.NoError(t, err) + require.NotEmpty(t, contractAddr) + + // check some account values + checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, contractStart) + checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance) + checkAccount(t, ctx, accKeeper, bankKeeper, fred, nil) + + // creator can send contract's tokens to fred (using SendMsg) + msg := wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: fred.String(), + Amount: []wasmvmtypes.Coin{{ + Denom: "denom", + Amount: "15000", + }}, + }, + }, + } + reflectSend := ReflectHandleMsg{ + ReflectSubCall: &reflectSubPayload{ + Msgs: []wasmvmtypes.SubMsg{{ + ID: 7, + Msg: msg, + }}, + }, + } + reflectSendBz, err := json.Marshal(reflectSend) + require.NoError(t, err) + _, err = keeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil) + require.NoError(t, err) + + // fred got coins + checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 15000))) + // contract lost them + checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 25000))) + checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance) + + // query the reflect state to ensure the result was stored + query := ReflectQueryMsg{ + SubCallResult: &SubCall{ID: 7}, + } + queryBz, err := json.Marshal(query) + require.NoError(t, err) + queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) + require.NoError(t, err) + + var res wasmvmtypes.Reply + err = json.Unmarshal(queryRes, &res) + require.NoError(t, err) + assert.Equal(t, uint64(7), res.ID) + assert.Empty(t, res.Result.Err) + require.NotNil(t, res.Result.Ok) + sub := res.Result.Ok + assert.Empty(t, sub.Data) + require.Len(t, sub.Events, 3) + + transfer := sub.Events[0] + assert.Equal(t, "transfer", transfer.Type) + assert.Equal(t, wasmvmtypes.EventAttribute{ + Key: "recipient", + Value: fred.String(), + }, transfer.Attributes[0]) + + sender := sub.Events[1] + assert.Equal(t, "message", sender.Type) + assert.Equal(t, wasmvmtypes.EventAttribute{ + Key: "sender", + Value: contractAddr.String(), + }, sender.Attributes[0]) + + // where does this come from? + module := sub.Events[2] + assert.Equal(t, "message", module.Type) + assert.Equal(t, wasmvmtypes.EventAttribute{ + Key: "module", + Value: "bank", + }, module.Attributes[0]) + +} + +func TestDispatchSubMsgErrorHandling(t *testing.T) { + fundedDenom := "funds" + fundedAmount := 1_000_000 + ctxGasLimit := uint64(1_000_000) + subGasLimit := uint64(300_000) + + // prep - create one chain and upload the code + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, nil, nil) + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper + contractStart := sdk.NewCoins(sdk.NewInt64Coin(fundedDenom, int64(fundedAmount))) + uploader := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, contractStart.Add(contractStart...)) + + // upload code + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + require.NoError(t, err) + reflectID, err := keeper.Create(ctx, uploader, reflectCode, "", "", nil) + require.NoError(t, err) + + // create hackatom contract for testing (for infinite loop) + hackatomCode, err := ioutil.ReadFile("./testdata/hackatom.wasm") + require.NoError(t, err) + hackatomID, err := keeper.Create(ctx, uploader, hackatomCode, "", "", nil) + require.NoError(t, err) + _, _, bob := keyPubAddr() + _, _, fred := keyPubAddr() + initMsg := HackatomExampleInitMsg{ + Verifier: fred, + Beneficiary: bob, + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + hackatomAddr, _, err := keeper.Instantiate(ctx, hackatomID, uploader, nil, initMsgBz, "hackatom demo", contractStart) + require.NoError(t, err) + + validBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { + return wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: emptyAccount, + Amount: []wasmvmtypes.Coin{{ + Denom: fundedDenom, + Amount: strconv.Itoa(fundedAmount / 2), + }}, + }, + }, + } + } + + invalidBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { + return wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: emptyAccount, + Amount: []wasmvmtypes.Coin{{ + Denom: fundedDenom, + Amount: strconv.Itoa(fundedAmount * 2), + }}, + }, + }, + } + } + + infiniteLoop := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { + return wasmvmtypes.CosmosMsg{ + Wasm: &wasmvmtypes.WasmMsg{ + Execute: &wasmvmtypes.ExecuteMsg{ + ContractAddr: hackatomAddr.String(), + Msg: []byte(`{"cpu_loop":{}}`), + }, + }, + } + } + + instantiateContract := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { + return wasmvmtypes.CosmosMsg{ + Wasm: &wasmvmtypes.WasmMsg{ + Instantiate: &wasmvmtypes.InstantiateMsg{ + CodeID: reflectID, + Msg: []byte("{}"), + Label: "subcall reflect", + }, + }, + } + } + + type assertion func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) + + assertReturnedEvents := func(expectedEvents int) assertion { + return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) { + assert.Len(t, response.Ok.Events, expectedEvents) + } + } + + assertGasUsed := func(minGas, maxGas uint64) assertion { + return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) { + gasUsed := ctx.GasMeter().GasConsumed() + assert.True(t, gasUsed >= minGas, "Used %d gas (less than expected %d)", gasUsed, minGas) + assert.True(t, gasUsed <= maxGas, "Used %d gas (more than expected %d)", gasUsed, maxGas) + } + } + + assertErrorString := func(shouldContain string) assertion { + return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) { + assert.Contains(t, response.Err, shouldContain) + } + } + + assertGotContractAddr := func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubcallResult) { + // should get the events emitted on new contract + event := response.Ok.Events[0] + assert.Equal(t, event.Type, "wasm") + assert.Equal(t, event.Attributes[0].Key, "contract_address") + eventAddr := event.Attributes[0].Value + assert.NotEqual(t, contract, eventAddr) + + // data field is the raw canonical address + // QUESTION: why not types.MsgInstantiateContractResponse? difference between calling Router and Service? + assert.Len(t, response.Ok.Data, 20) + resAddr := sdk.AccAddress(response.Ok.Data) + assert.Equal(t, eventAddr, resAddr.String()) + } + + cases := map[string]struct { + submsgID uint64 + // we will generate message from the + msg func(contract, emptyAccount string) wasmvmtypes.CosmosMsg + gasLimit *uint64 + + // true if we expect this to throw out of gas panic + isOutOfGasPanic bool + // true if we expect this execute to return an error (can be false when submessage errors) + executeError bool + // true if we expect submessage to return an error (but execute to return success) + subMsgError bool + // make assertions after dispatch + resultAssertions []assertion + }{ + "send tokens": { + submsgID: 5, + msg: validBankSend, + // note we charge another 40k for the reply call + resultAssertions: []assertion{assertReturnedEvents(3), assertGasUsed(123000, 125000)}, + }, + "not enough tokens": { + submsgID: 6, + msg: invalidBankSend, + subMsgError: true, + // uses less gas than the send tokens (cost of bank transfer) + resultAssertions: []assertion{assertGasUsed(97000, 99000), assertErrorString("insufficient funds")}, + }, + "out of gas panic with no gas limit": { + submsgID: 7, + msg: infiniteLoop, + isOutOfGasPanic: true, + }, + + "send tokens with limit": { + submsgID: 15, + msg: validBankSend, + gasLimit: &subGasLimit, + // uses same gas as call without limit + resultAssertions: []assertion{assertReturnedEvents(3), assertGasUsed(123000, 125000)}, + }, + "not enough tokens with limit": { + submsgID: 16, + msg: invalidBankSend, + subMsgError: true, + gasLimit: &subGasLimit, + // uses same gas as call without limit + resultAssertions: []assertion{assertGasUsed(97000, 99000), assertErrorString("insufficient funds")}, + }, + "out of gas caught with gas limit": { + submsgID: 17, + msg: infiniteLoop, + subMsgError: true, + gasLimit: &subGasLimit, + // uses all the subGasLimit, plus the 92k or so for the main contract + resultAssertions: []assertion{assertGasUsed(subGasLimit+92000, subGasLimit+94000), assertErrorString("out of gas")}, + }, + + "instantiate contract gets address in data and events": { + submsgID: 21, + msg: instantiateContract, + resultAssertions: []assertion{assertReturnedEvents(1), assertGotContractAddr}, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, contractStart) + _, _, empty := keyPubAddr() + + contractAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), fmt.Sprintf("contract %s", name), contractStart) + require.NoError(t, err) + + msg := tc.msg(contractAddr.String(), empty.String()) + reflectSend := ReflectHandleMsg{ + ReflectSubCall: &reflectSubPayload{ + Msgs: []wasmvmtypes.SubMsg{{ + ID: tc.submsgID, + Msg: msg, + GasLimit: tc.gasLimit, + }}, + }, + } + reflectSendBz, err := json.Marshal(reflectSend) + require.NoError(t, err) + + execCtx := ctx.WithGasMeter(sdk.NewGasMeter(ctxGasLimit)) + defer func() { + if tc.isOutOfGasPanic { + r := recover() + require.NotNil(t, r, "expected panic") + if _, ok := r.(sdk.ErrorOutOfGas); !ok { + t.Fatalf("Expected OutOfGas panic, got: %#v\n", r) + } + } + }() + _, err = keeper.Execute(execCtx, contractAddr, creator, reflectSendBz, nil) + + if tc.executeError { + require.Error(t, err) + } else { + require.NoError(t, err) + + // query the reply + query := ReflectQueryMsg{ + SubCallResult: &SubCall{ID: tc.submsgID}, + } + queryBz, err := json.Marshal(query) + require.NoError(t, err) + queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) + require.NoError(t, err) + var res wasmvmtypes.Reply + err = json.Unmarshal(queryRes, &res) + require.NoError(t, err) + assert.Equal(t, tc.submsgID, res.ID) + + if tc.subMsgError { + require.NotEmpty(t, res.Result.Err) + require.Nil(t, res.Result.Ok) + } else { + require.Empty(t, res.Result.Err) + require.NotNil(t, res.Result.Ok) + } + + for _, assertion := range tc.resultAssertions { + assertion(t, execCtx, contractAddr.String(), empty.String(), res.Result) + } + + } + }) + } +} + +// Test an error case, where the Encoded doesn't return any sdk.Msg and we trigger(ed) a null pointer exception. +// This occurs with the IBC encoder. Test this. +func TestDispatchSubMsgEncodeToNoSdkMsg(t *testing.T) { + // fake out the bank handle to return success with no data + nilEncoder := func(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]sdk.Msg, error) { + return nil, nil + } + customEncoders := &MessageEncoders{ + Bank: nilEncoder, + } + + ctx, keepers := CreateTestInput(t, false, ReflectFeatures, customEncoders, nil) + accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) + + creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit) + _, _, fred := keyPubAddr() + + // upload code + reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + require.NoError(t, err) + codeID, err := keeper.Create(ctx, creator, reflectCode, "", "", nil) + require.NoError(t, err) + + // creator instantiates a contract and gives it tokens + contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) + require.NoError(t, err) + require.NotEmpty(t, contractAddr) + + // creator can send contract's tokens to fred (using SendMsg) + msg := wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: fred.String(), + Amount: []wasmvmtypes.Coin{{ + Denom: "denom", + Amount: "15000", + }}, + }, + }, + } + reflectSend := ReflectHandleMsg{ + ReflectSubCall: &reflectSubPayload{ + Msgs: []wasmvmtypes.SubMsg{{ + ID: 7, + Msg: msg, + }}, + }, + } + reflectSendBz, err := json.Marshal(reflectSend) + require.NoError(t, err) + _, err = keeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil) + require.NoError(t, err) + + // query the reflect state to ensure the result was stored + query := ReflectQueryMsg{ + SubCallResult: &SubCall{ID: 7}, + } + queryBz, err := json.Marshal(query) + require.NoError(t, err) + queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) + require.NoError(t, err) + + var res wasmvmtypes.Reply + err = json.Unmarshal(queryRes, &res) + require.NoError(t, err) + assert.Equal(t, uint64(7), res.ID) + assert.Empty(t, res.Result.Err) + require.NotNil(t, res.Result.Ok) + sub := res.Result.Ok + assert.Empty(t, sub.Data) + require.Len(t, sub.Events, 0) +} diff --git a/x/wasm/internal/keeper/wasmtesting/messenger.go b/x/wasm/internal/keeper/wasmtesting/messenger.go index 5c510a16..ae964011 100644 --- a/x/wasm/internal/keeper/wasmtesting/messenger.go +++ b/x/wasm/internal/keeper/wasmtesting/messenger.go @@ -1,24 +1,35 @@ package wasmtesting import ( + "errors" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" ) type MockMessageHandler struct { - DispatchFn func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error + DispatchMsgFn func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) } -func (m *MockMessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - return m.DispatchFn(ctx, contractAddr, contractIBCPortID, msgs...) +func (m *MockMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + return m.DispatchMsgFn(ctx, contractAddr, contractIBCPortID, msg) } func NewCapturingMessageHandler() (*MockMessageHandler, *[]wasmvmtypes.CosmosMsg) { var messages []wasmvmtypes.CosmosMsg return &MockMessageHandler{ - DispatchFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msgs ...wasmvmtypes.CosmosMsg) error { - messages = append(messages, msgs...) - return nil + DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + messages = append(messages, msg) + // return one data item so that this doesn't cause an error in submessage processing (it takes the first element from data) + return nil, [][]byte{{1}}, nil }, }, &messages } + +func NewErroringMessageHandler() *MockMessageHandler { + return &MockMessageHandler{ + DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + return nil, nil, errors.New("test, ignore") + }, + } +} diff --git a/x/wasm/internal/keeper/wasmtesting/mock_engine.go b/x/wasm/internal/keeper/wasmtesting/mock_engine.go index c2da0fa4..5a8ae940 100644 --- a/x/wasm/internal/keeper/wasmtesting/mock_engine.go +++ b/x/wasm/internal/keeper/wasmtesting/mock_engine.go @@ -22,6 +22,7 @@ type MockWasmer struct { QueryFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) ([]byte, uint64, error) MigrateFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error) SudoFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error) + ReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error) GetCodeFn func(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) CleanupFn func() IBCChannelOpenFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, channel wasmvmtypes.IBCChannel, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (uint64, error) @@ -126,6 +127,13 @@ func (m *MockWasmer) Sudo(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg [ } +func (m *MockWasmer) Reply(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error) { + if m.ReplyFn == nil { + panic("not supposed to be called!") + } + return m.ReplyFn(codeID, env, reply, store, goapi, querier, gasMeter, gasLimit) +} + func (m *MockWasmer) GetCode(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) { if m.GetCodeFn == nil { panic("not supposed to be called!") diff --git a/x/wasm/internal/types/wasmer_engine.go b/x/wasm/internal/types/wasmer_engine.go index 2d4b0d89..501d6871 100644 --- a/x/wasm/internal/types/wasmer_engine.go +++ b/x/wasm/internal/types/wasmer_engine.go @@ -107,6 +107,18 @@ type WasmerEngine interface { gasLimit uint64, ) (*wasmvmtypes.Response, uint64, error) + // Reply is called on the original dispatching contract after running a submessage + Reply( + codeID wasmvm.Checksum, + env wasmvmtypes.Env, + reply wasmvmtypes.Reply, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + ) (*wasmvmtypes.Response, uint64, error) + // GetCode will load the original wasm code for the given code id. // This will only succeed if that code id was previously returned from // a call to Create.