Submessage reply can overwrite caller response (#502)
* Reply may overwrite result data * Fix interface name * Refacting for tests * Test response handler * Fix naked error
This commit is contained in:
@@ -14,7 +14,6 @@ import (
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"path/filepath"
|
||||
@@ -51,33 +50,41 @@ type Option interface {
|
||||
}
|
||||
|
||||
// WasmVMQueryHandler is an extension point for custom query handler implementations
|
||||
type WASMVMQueryHandler interface {
|
||||
type WasmVMQueryHandler interface {
|
||||
// HandleQuery executes the requested query
|
||||
HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error)
|
||||
}
|
||||
|
||||
// Messenger is an extension point for custom wasmVM message handling
|
||||
type Messenger interface {
|
||||
// DispatchMsg encodes the wasmVM message and dispatches it.
|
||||
DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error)
|
||||
}
|
||||
|
||||
type CoinTransferrer interface {
|
||||
// TransferCoins sends the coin amounts from the source to the destination with rules applied.
|
||||
TransferCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
|
||||
}
|
||||
|
||||
// WasmVMResponseHandler is an extension point to handles the response data returned by a contract call.
|
||||
type WasmVMResponseHandler interface {
|
||||
// Handle processes the data returned by a contract invocation.
|
||||
Handle(
|
||||
ctx sdk.Context,
|
||||
contractAddr sdk.AccAddress,
|
||||
ibcPort string,
|
||||
submessages []wasmvmtypes.SubMsg,
|
||||
messages []wasmvmtypes.CosmosMsg,
|
||||
origRspData []byte,
|
||||
) ([]byte, error)
|
||||
}
|
||||
|
||||
// Keeper will have a reference to Wasmer with it's own data directory.
|
||||
type Keeper struct {
|
||||
storeKey sdk.StoreKey
|
||||
cdc codec.Marshaler
|
||||
accountKeeper types.AccountKeeper
|
||||
bank CoinTransferrer
|
||||
portKeeper types.PortKeeper
|
||||
capabilityKeeper types.CapabilityKeeper
|
||||
wasmVM types.WasmerEngine
|
||||
wasmVMQueryHandler WASMVMQueryHandler
|
||||
messenger Messenger
|
||||
storeKey sdk.StoreKey
|
||||
cdc codec.Marshaler
|
||||
accountKeeper types.AccountKeeper
|
||||
bank CoinTransferrer
|
||||
portKeeper types.PortKeeper
|
||||
capabilityKeeper types.CapabilityKeeper
|
||||
wasmVM types.WasmerEngine
|
||||
wasmVMQueryHandler WasmVMQueryHandler
|
||||
wasmVMResponseHandler WasmVMResponseHandler
|
||||
messenger Messenger
|
||||
// queryGasLimit is the max wasmvm gas that can be spent on executing a query with a contract
|
||||
queryGasLimit uint64
|
||||
paramSpace paramtypes.Subspace
|
||||
@@ -113,7 +120,7 @@ func NewKeeper(
|
||||
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
|
||||
}
|
||||
|
||||
keeper := Keeper{
|
||||
keeper := &Keeper{
|
||||
storeKey: storeKey,
|
||||
cdc: cdc,
|
||||
wasmVM: wasmer,
|
||||
@@ -125,11 +132,14 @@ func NewKeeper(
|
||||
queryGasLimit: wasmConfig.SmartQueryGasLimit,
|
||||
paramSpace: paramSpace,
|
||||
}
|
||||
keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, channelKeeper, queryRouter, &keeper)
|
||||
|
||||
keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, channelKeeper, queryRouter, keeper)
|
||||
for _, o := range opts {
|
||||
o.apply(&keeper)
|
||||
o.apply(keeper)
|
||||
}
|
||||
return keeper
|
||||
// not updateable, yet
|
||||
keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper))
|
||||
return *keeper
|
||||
}
|
||||
|
||||
func (k Keeper) getUploadAccessConfig(ctx sdk.Context) types.AccessConfig {
|
||||
@@ -302,12 +312,12 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A
|
||||
k.storeContractInfo(ctx, contractAddress, &contractInfo)
|
||||
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res)
|
||||
if err != nil {
|
||||
return nil, nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return contractAddress, res.Data, nil
|
||||
return contractAddress, data, nil
|
||||
}
|
||||
|
||||
// Execute executes the contract instance
|
||||
@@ -346,13 +356,13 @@ func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return &sdk.Result{
|
||||
Data: res.Data,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -418,13 +428,13 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
|
||||
k.storeContractInfo(ctx, contractAddress, contractInfo)
|
||||
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return &sdk.Result{
|
||||
Data: res.Data,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -458,13 +468,13 @@ func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return &sdk.Result{
|
||||
Data: res.Data,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -500,13 +510,12 @@ func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply was
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
// dispatch submessages then messages
|
||||
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
|
||||
data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "dispatch")
|
||||
}
|
||||
|
||||
return &sdk.Result{
|
||||
Data: res.Data,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -803,146 +812,9 @@ func (k Keeper) setContractInfoExtension(ctx sdk.Context, contractAddr sdk.AccAd
|
||||
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)
|
||||
|
||||
// we only callback if requested. Short-circuit here the two cases we don't want to
|
||||
if msg.ReplyOn == wasmvmtypes.ReplySuccess && err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.ReplyOn == wasmvmtypes.ReplyError && err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// otherwise, we create a SubcallResult and pass it into the calling contract
|
||||
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
|
||||
// handleContractResponse processes the contract response
|
||||
func (k *Keeper) handleContractResponse(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, res *wasmvmtypes.Response) ([]byte, error) {
|
||||
return k.wasmVMResponseHandler.Handle(ctx, contractAddr, ibcPort, res.Submessages, res.Messages, res.Data)
|
||||
}
|
||||
|
||||
func gasForContract(ctx sdk.Context) uint64 {
|
||||
@@ -1111,3 +983,39 @@ func (c BankCoinTransferrer) TransferCoins(ctx sdk.Context, fromAddr sdk.AccAddr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type msgDispatcher interface {
|
||||
DispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error
|
||||
DispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error)
|
||||
}
|
||||
|
||||
// DefaultWasmVMContractResponseHandler default implementation that first dispatches submessage then normal messages.
|
||||
// The Submessage execution may include an success/failure response handling by the contract that can overwrite the
|
||||
// original
|
||||
type DefaultWasmVMContractResponseHandler struct {
|
||||
md msgDispatcher
|
||||
}
|
||||
|
||||
func NewDefaultWasmVMContractResponseHandler(md msgDispatcher) *DefaultWasmVMContractResponseHandler {
|
||||
return &DefaultWasmVMContractResponseHandler{md: md}
|
||||
}
|
||||
|
||||
// Handle processes the data returned by a contract invocation.
|
||||
func (h DefaultWasmVMContractResponseHandler) Handle(
|
||||
ctx sdk.Context,
|
||||
contractAddr sdk.AccAddress,
|
||||
ibcPort string,
|
||||
submessages []wasmvmtypes.SubMsg,
|
||||
messages []wasmvmtypes.CosmosMsg,
|
||||
origRspData []byte,
|
||||
) ([]byte, error) {
|
||||
result := origRspData
|
||||
switch rsp, err := h.md.DispatchSubmessages(ctx, contractAddr, ibcPort, submessages); {
|
||||
case err != nil:
|
||||
return nil, sdkerrors.Wrap(err, "submessages")
|
||||
case rsp != nil:
|
||||
result = rsp
|
||||
}
|
||||
// then dispatch all the normal messages
|
||||
return result, sdkerrors.Wrap(h.md.DispatchMessages(ctx, contractAddr, ibcPort, messages), "messages")
|
||||
}
|
||||
|
||||
@@ -1386,3 +1386,90 @@ func TestInitializePinnedCodes(t *testing.T) {
|
||||
assert.Equal(t, exp, capturedChecksums[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDefaultWasmVMContractResponseHandler(t *testing.T) {
|
||||
noopDMsgs := func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
specs := map[string]struct {
|
||||
srcData []byte
|
||||
setup func(m *wasmtesting.MockMsgDispatcher)
|
||||
expErr bool
|
||||
expData []byte
|
||||
}{
|
||||
"submessage overwrites result when set": {
|
||||
srcData: []byte("otherData"),
|
||||
setup: func(m *wasmtesting.MockMsgDispatcher) {
|
||||
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
|
||||
return []byte("mySubMsgData"), nil
|
||||
}
|
||||
m.DispatchMessagesFn = noopDMsgs
|
||||
},
|
||||
expErr: false,
|
||||
expData: []byte("mySubMsgData"),
|
||||
},
|
||||
"submessage overwrites result when empty": {
|
||||
srcData: []byte("otherData"),
|
||||
setup: func(m *wasmtesting.MockMsgDispatcher) {
|
||||
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
m.DispatchMessagesFn = noopDMsgs
|
||||
},
|
||||
expErr: false,
|
||||
expData: []byte(""),
|
||||
},
|
||||
"submessage do not overwrite result when nil": {
|
||||
srcData: []byte("otherData"),
|
||||
setup: func(m *wasmtesting.MockMsgDispatcher) {
|
||||
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
m.DispatchMessagesFn = noopDMsgs
|
||||
},
|
||||
expErr: false,
|
||||
expData: []byte("otherData"),
|
||||
},
|
||||
"submessage error aborts process": {
|
||||
setup: func(m *wasmtesting.MockMsgDispatcher) {
|
||||
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
|
||||
return nil, errors.New("test - ignore")
|
||||
}
|
||||
},
|
||||
expErr: true,
|
||||
},
|
||||
"message error aborts process": {
|
||||
setup: func(m *wasmtesting.MockMsgDispatcher) {
|
||||
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
|
||||
return []byte("mySubMsgData"), nil
|
||||
}
|
||||
m.DispatchMessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error {
|
||||
return errors.New("test - ignore")
|
||||
}
|
||||
},
|
||||
expErr: true,
|
||||
},
|
||||
}
|
||||
for name, spec := range specs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var (
|
||||
subMsgs []wasmvmtypes.SubMsg
|
||||
msgs []wasmvmtypes.CosmosMsg
|
||||
)
|
||||
var mock wasmtesting.MockMsgDispatcher
|
||||
spec.setup(&mock)
|
||||
d := NewDefaultWasmVMContractResponseHandler(&mock)
|
||||
// when
|
||||
|
||||
gotData, gotErr := d.Handle(sdk.Context{}, RandomAccountAddress(t), "ibc-port", subMsgs, msgs, spec.srcData)
|
||||
if spec.expErr {
|
||||
require.Error(t, gotErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, gotErr)
|
||||
assert.Equal(t, spec.expData, gotData)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
174
x/wasm/keeper/msg_dispatcher.go
Normal file
174
x/wasm/keeper/msg_dispatcher.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"github.com/CosmWasm/wasmd/x/wasm/types"
|
||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
// Messenger is an extension point for custom wasmd message handling
|
||||
type Messenger interface {
|
||||
// DispatchMsg encodes the wasmVM message and dispatches it.
|
||||
DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error)
|
||||
}
|
||||
|
||||
// replyer is a subset of keeper that can handle replies to submessages
|
||||
type replyer interface {
|
||||
reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error)
|
||||
}
|
||||
|
||||
// MessageDispatcher coordinates message sending and submessage reply/ state commits
|
||||
type MessageDispatcher struct {
|
||||
messenger Messenger
|
||||
keeper replyer
|
||||
}
|
||||
|
||||
// NewMessageDispatcher constructor
|
||||
func NewMessageDispatcher(messenger Messenger, keeper replyer) *MessageDispatcher {
|
||||
return &MessageDispatcher{messenger: messenger, keeper: keeper}
|
||||
}
|
||||
|
||||
// DispatchMessages sends all messages.
|
||||
func (d MessageDispatcher) DispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error {
|
||||
for _, msg := range msgs {
|
||||
events, _, err := d.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
|
||||
}
|
||||
|
||||
// dispatchMsgWithGasLimit sends a message with gas limit applied
|
||||
func (d MessageDispatcher) 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
|
||||
moduleLogger(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 = d.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 (d MessageDispatcher) DispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
|
||||
var rsp []byte
|
||||
for _, msg := range msgs {
|
||||
switch msg.ReplyOn {
|
||||
case wasmvmtypes.ReplySuccess, wasmvmtypes.ReplyError, wasmvmtypes.ReplyAlways:
|
||||
default:
|
||||
return nil, sdkerrors.Wrap(types.ErrInvalid, "replyOn")
|
||||
}
|
||||
// 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 = d.dispatchMsgWithGasLimit(subCtx, contractAddr, ibcPort, msg.Msg, *msg.GasLimit)
|
||||
} else {
|
||||
events, data, err = d.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)
|
||||
|
||||
// we only callback if requested. Short-circuit here the two cases we don't want to
|
||||
if msg.ReplyOn == wasmvmtypes.ReplySuccess && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.ReplyOn == wasmvmtypes.ReplyError && err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// otherwise, we create a SubcallResult and pass it into the calling contract
|
||||
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()
|
||||
rData, err := d.keeper.reply(ctx, contractAddr, reply)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, sdkerrors.Wrap(err, "reply")
|
||||
case rData.Data != nil:
|
||||
rsp = rData.Data
|
||||
}
|
||||
}
|
||||
return rsp, 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
|
||||
}
|
||||
236
x/wasm/keeper/msg_dispatcher_test.go
Normal file
236
x/wasm/keeper/msg_dispatcher_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
|
||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDispatchSubmessages(t *testing.T) {
|
||||
noReplyCalled := &mockReplyer{}
|
||||
var anyGasLimit uint64 = 1
|
||||
specs := map[string]struct {
|
||||
msgs []wasmvmtypes.SubMsg
|
||||
replyer *mockReplyer
|
||||
msgHandler *wasmtesting.MockMessageHandler
|
||||
expErr bool
|
||||
expData []byte
|
||||
expCommits []bool
|
||||
expEvents sdk.Events
|
||||
}{
|
||||
"no reply on error without error": {
|
||||
msgs: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyError}},
|
||||
replyer: noReplyCalled,
|
||||
msgHandler: &wasmtesting.MockMessageHandler{
|
||||
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
return nil, [][]byte{[]byte("myData")}, nil
|
||||
},
|
||||
},
|
||||
expCommits: []bool{true},
|
||||
},
|
||||
"no reply on success without success": {
|
||||
msgs: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplySuccess}},
|
||||
replyer: noReplyCalled,
|
||||
msgHandler: &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, errors.New("test, ignore")
|
||||
},
|
||||
},
|
||||
expCommits: []bool{false},
|
||||
expErr: true,
|
||||
},
|
||||
"reply on success - received": {
|
||||
msgs: []wasmvmtypes.SubMsg{{
|
||||
ReplyOn: wasmvmtypes.ReplySuccess,
|
||||
}},
|
||||
replyer: &mockReplyer{
|
||||
replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
return &sdk.Result{Data: []byte("myReplyData")}, nil
|
||||
},
|
||||
},
|
||||
msgHandler: &wasmtesting.MockMessageHandler{
|
||||
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
return nil, [][]byte{[]byte("myData")}, nil
|
||||
},
|
||||
},
|
||||
expData: []byte("myReplyData"),
|
||||
expCommits: []bool{true},
|
||||
},
|
||||
"reply on error - handled": {
|
||||
msgs: []wasmvmtypes.SubMsg{{
|
||||
ReplyOn: wasmvmtypes.ReplyError,
|
||||
}},
|
||||
replyer: &mockReplyer{
|
||||
replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
return &sdk.Result{Data: []byte("myReplyData")}, nil
|
||||
},
|
||||
},
|
||||
msgHandler: &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, errors.New("my error")
|
||||
},
|
||||
},
|
||||
expData: []byte("myReplyData"),
|
||||
expCommits: []bool{false},
|
||||
},
|
||||
"with reply events": {
|
||||
msgs: []wasmvmtypes.SubMsg{{
|
||||
ReplyOn: wasmvmtypes.ReplySuccess,
|
||||
}},
|
||||
replyer: &mockReplyer{
|
||||
replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
return &sdk.Result{Data: []byte("myReplyData")}, nil
|
||||
},
|
||||
},
|
||||
msgHandler: &wasmtesting.MockMessageHandler{
|
||||
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
myEvents := []sdk.Event{{Type: "myEvent", Attributes: []abci.EventAttribute{{Key: []byte("foo"), Value: []byte("bar")}}}}
|
||||
return myEvents, [][]byte{[]byte("myData")}, nil
|
||||
},
|
||||
},
|
||||
expData: []byte("myReplyData"),
|
||||
expCommits: []bool{true},
|
||||
expEvents: []sdk.Event{{
|
||||
Type: "myEvent",
|
||||
Attributes: []abci.EventAttribute{{Key: []byte("foo"), Value: []byte("bar")}},
|
||||
}},
|
||||
},
|
||||
"reply returns error": {
|
||||
msgs: []wasmvmtypes.SubMsg{{
|
||||
ReplyOn: wasmvmtypes.ReplySuccess,
|
||||
}},
|
||||
replyer: &mockReplyer{
|
||||
replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
return nil, errors.New("reply failed")
|
||||
},
|
||||
},
|
||||
msgHandler: &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, nil
|
||||
},
|
||||
},
|
||||
expCommits: []bool{false},
|
||||
expErr: true,
|
||||
},
|
||||
"with gas limit - out of gas": {
|
||||
msgs: []wasmvmtypes.SubMsg{{
|
||||
GasLimit: &anyGasLimit,
|
||||
ReplyOn: wasmvmtypes.ReplyError,
|
||||
}},
|
||||
replyer: &mockReplyer{
|
||||
replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
return &sdk.Result{Data: []byte("myReplyData")}, nil
|
||||
},
|
||||
},
|
||||
msgHandler: &wasmtesting.MockMessageHandler{
|
||||
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
ctx.GasMeter().ConsumeGas(sdk.Gas(101), "testing")
|
||||
return nil, [][]byte{[]byte("someData")}, nil
|
||||
},
|
||||
},
|
||||
expData: []byte("myReplyData"),
|
||||
expCommits: []bool{false},
|
||||
},
|
||||
"with gas limit - within limit no error": {
|
||||
msgs: []wasmvmtypes.SubMsg{{
|
||||
GasLimit: &anyGasLimit,
|
||||
ReplyOn: wasmvmtypes.ReplyError,
|
||||
}},
|
||||
replyer: &mockReplyer{},
|
||||
msgHandler: &wasmtesting.MockMessageHandler{
|
||||
DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
|
||||
ctx.GasMeter().ConsumeGas(sdk.Gas(1), "testing")
|
||||
return nil, [][]byte{[]byte("someData")}, nil
|
||||
},
|
||||
},
|
||||
expCommits: []bool{true},
|
||||
},
|
||||
"multiple msg - last reply": {
|
||||
msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}},
|
||||
replyer: &mockReplyer{
|
||||
replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
return &sdk.Result{Data: []byte(fmt.Sprintf("myReplyData:%d", reply.ID))}, nil
|
||||
},
|
||||
},
|
||||
msgHandler: &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, errors.New("my error")
|
||||
},
|
||||
},
|
||||
expData: []byte("myReplyData:2"),
|
||||
expCommits: []bool{false, false},
|
||||
},
|
||||
"multiple msg - last reply with non nil": {
|
||||
msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}},
|
||||
replyer: &mockReplyer{
|
||||
replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
if reply.ID == 2 {
|
||||
return &sdk.Result{}, nil
|
||||
}
|
||||
return &sdk.Result{Data: []byte("myReplyData:1")}, nil
|
||||
},
|
||||
},
|
||||
msgHandler: &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, errors.New("my error")
|
||||
},
|
||||
},
|
||||
expData: []byte("myReplyData:1"),
|
||||
expCommits: []bool{false, false},
|
||||
},
|
||||
"empty replyOn rejected": {
|
||||
msgs: []wasmvmtypes.SubMsg{{}},
|
||||
replyer: noReplyCalled,
|
||||
msgHandler: &wasmtesting.MockMessageHandler{},
|
||||
expErr: true,
|
||||
},
|
||||
"invalid replyOn rejected": {
|
||||
msgs: []wasmvmtypes.SubMsg{{ReplyOn: "invalid"}},
|
||||
replyer: noReplyCalled,
|
||||
msgHandler: &wasmtesting.MockMessageHandler{},
|
||||
expCommits: []bool{false},
|
||||
expErr: true,
|
||||
},
|
||||
}
|
||||
for name, spec := range specs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var mockStore wasmtesting.MockCommitMultiStore
|
||||
em := sdk.NewEventManager()
|
||||
ctx := sdk.Context{}.WithMultiStore(&mockStore).
|
||||
WithGasMeter(sdk.NewGasMeter(100)).
|
||||
WithEventManager(em)
|
||||
d := NewMessageDispatcher(spec.msgHandler, spec.replyer)
|
||||
gotData, gotErr := d.DispatchSubmessages(ctx, RandomAccountAddress(t), "any_port", spec.msgs)
|
||||
if spec.expErr {
|
||||
require.Error(t, gotErr)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, gotErr)
|
||||
assert.Equal(t, spec.expData, gotData)
|
||||
}
|
||||
assert.Equal(t, spec.expCommits, mockStore.Committed)
|
||||
if len(spec.expEvents) == 0 {
|
||||
assert.Empty(t, em.Events())
|
||||
} else {
|
||||
assert.Equal(t, spec.expEvents, em.Events())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockReplyer struct {
|
||||
replyFn func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error)
|
||||
}
|
||||
|
||||
func (m mockReplyer) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) (*sdk.Result, error) {
|
||||
if m.replyFn == nil {
|
||||
panic("not expected to be called")
|
||||
}
|
||||
return m.replyFn(ctx, contractAddress, reply)
|
||||
}
|
||||
@@ -11,7 +11,7 @@ func (f optsFn) apply(keeper *Keeper) {
|
||||
f(keeper)
|
||||
}
|
||||
|
||||
// WithMessageHandler is an optional constructor parameter to replace the default wasmVM engine with the
|
||||
// WithWasmEngine is an optional constructor parameter to replace the default wasmVM engine with the
|
||||
// given one.
|
||||
func WithWasmEngine(x types.WasmerEngine) Option {
|
||||
return optsFn(func(k *Keeper) {
|
||||
@@ -29,7 +29,7 @@ func WithMessageHandler(x Messenger) Option {
|
||||
|
||||
// WithQueryHandler is an optional constructor parameter to set custom query handler for wasmVM requests.
|
||||
// This option should not be combined with Option `WithQueryPlugins`.
|
||||
func WithQueryHandler(x WASMVMQueryHandler) Option {
|
||||
func WithQueryHandler(x WasmVMQueryHandler) Option {
|
||||
return optsFn(func(k *Keeper) {
|
||||
k.wasmVMQueryHandler = x
|
||||
})
|
||||
|
||||
@@ -16,11 +16,11 @@ import (
|
||||
|
||||
type QueryHandler struct {
|
||||
Ctx sdk.Context
|
||||
Plugins WASMVMQueryHandler
|
||||
Plugins WasmVMQueryHandler
|
||||
Caller sdk.AccAddress
|
||||
}
|
||||
|
||||
func NewQueryHandler(ctx sdk.Context, vmQueryHandler WASMVMQueryHandler, caller sdk.AccAddress) QueryHandler {
|
||||
func NewQueryHandler(ctx sdk.Context, vmQueryHandler WasmVMQueryHandler, caller sdk.AccAddress) QueryHandler {
|
||||
return QueryHandler{
|
||||
Ctx: ctx,
|
||||
Plugins: vmQueryHandler,
|
||||
|
||||
@@ -71,7 +71,7 @@ func (k Keeper) OnConnectChannel(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages); err != nil {
|
||||
if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -109,7 +109,7 @@ func (k Keeper) OnCloseChannel(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages); err != nil {
|
||||
if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -145,11 +145,7 @@ func (k Keeper) OnRecvPacket(
|
||||
// emit all events from this contract itself
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Acknowledgement, nil
|
||||
return k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, res.Acknowledgement)
|
||||
}
|
||||
|
||||
// OnAckPacket calls the contract to handle the "acknowledgement" data which can contain success or failure of a packet
|
||||
@@ -184,7 +180,7 @@ func (k Keeper) OnAckPacket(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages); err != nil {
|
||||
if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -219,7 +215,7 @@ func (k Keeper) OnTimeoutPacket(
|
||||
events := types.ParseEvents(res.Attributes, contractAddr)
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
if err := k.dispatchAll(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages); err != nil {
|
||||
if _, err := k.wasmVMResponseHandler.Handle(ctx, contractAddr, contractInfo.IBCPortID, res.Submessages, res.Messages, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
func TestOnOpenChannel(t *testing.T) {
|
||||
var m wasmtesting.MockWasmer
|
||||
wasmtesting.MakeIBCInstantiable(&m)
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures)
|
||||
var messenger = &wasmtesting.MockMessageHandler{}
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger))
|
||||
example := SeedNewContractInstance(t, parentCtx, keepers, &m)
|
||||
|
||||
specs := map[string]struct {
|
||||
@@ -74,7 +75,8 @@ func TestOnOpenChannel(t *testing.T) {
|
||||
func TestOnConnectChannel(t *testing.T) {
|
||||
var m wasmtesting.MockWasmer
|
||||
wasmtesting.MakeIBCInstantiable(&m)
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures)
|
||||
var messenger = &wasmtesting.MockMessageHandler{}
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger))
|
||||
example := SeedNewContractInstance(t, parentCtx, keepers, &m)
|
||||
|
||||
specs := map[string]struct {
|
||||
@@ -82,7 +84,7 @@ func TestOnConnectChannel(t *testing.T) {
|
||||
contractGas sdk.Gas
|
||||
contractResp *wasmvmtypes.IBCBasicResponse
|
||||
contractErr error
|
||||
overwriteMessenger Messenger
|
||||
overwriteMessenger *wasmtesting.MockMessageHandler
|
||||
expErr bool
|
||||
expContractEventAttrs int
|
||||
expNoEvents bool
|
||||
@@ -148,10 +150,9 @@ func TestOnConnectChannel(t *testing.T) {
|
||||
defer cancel()
|
||||
before := ctx.GasMeter().GasConsumed()
|
||||
msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler()
|
||||
keepers.WasmKeeper.messenger = msger
|
||||
|
||||
*messenger = *msger
|
||||
if spec.overwriteMessenger != nil {
|
||||
keepers.WasmKeeper.messenger = spec.overwriteMessenger
|
||||
*messenger = *spec.overwriteMessenger
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -184,7 +185,8 @@ func TestOnConnectChannel(t *testing.T) {
|
||||
func TestOnCloseChannel(t *testing.T) {
|
||||
var m wasmtesting.MockWasmer
|
||||
wasmtesting.MakeIBCInstantiable(&m)
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures)
|
||||
var messenger = &wasmtesting.MockMessageHandler{}
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger))
|
||||
example := SeedNewContractInstance(t, parentCtx, keepers, &m)
|
||||
|
||||
specs := map[string]struct {
|
||||
@@ -192,7 +194,7 @@ func TestOnCloseChannel(t *testing.T) {
|
||||
contractGas sdk.Gas
|
||||
contractResp *wasmvmtypes.IBCBasicResponse
|
||||
contractErr error
|
||||
overwriteMessenger Messenger
|
||||
overwriteMessenger *wasmtesting.MockMessageHandler
|
||||
expErr bool
|
||||
expContractEventAttrs int
|
||||
expNoEvents bool
|
||||
@@ -257,10 +259,10 @@ func TestOnCloseChannel(t *testing.T) {
|
||||
defer cancel()
|
||||
before := ctx.GasMeter().GasConsumed()
|
||||
msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler()
|
||||
keepers.WasmKeeper.messenger = msger
|
||||
*messenger = *msger
|
||||
|
||||
if spec.overwriteMessenger != nil {
|
||||
keepers.WasmKeeper.messenger = spec.overwriteMessenger
|
||||
*messenger = *spec.overwriteMessenger
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -294,7 +296,8 @@ func TestOnCloseChannel(t *testing.T) {
|
||||
func TestOnRecvPacket(t *testing.T) {
|
||||
var m wasmtesting.MockWasmer
|
||||
wasmtesting.MakeIBCInstantiable(&m)
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures)
|
||||
var messenger = &wasmtesting.MockMessageHandler{}
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger))
|
||||
example := SeedNewContractInstance(t, parentCtx, keepers, &m)
|
||||
|
||||
specs := map[string]struct {
|
||||
@@ -302,7 +305,7 @@ func TestOnRecvPacket(t *testing.T) {
|
||||
contractGas sdk.Gas
|
||||
contractResp *wasmvmtypes.IBCReceiveResponse
|
||||
contractErr error
|
||||
overwriteMessenger Messenger
|
||||
overwriteMessenger *wasmtesting.MockMessageHandler
|
||||
expErr bool
|
||||
expContractEventAttrs int
|
||||
expNoEvents bool
|
||||
@@ -380,10 +383,10 @@ func TestOnRecvPacket(t *testing.T) {
|
||||
before := ctx.GasMeter().GasConsumed()
|
||||
|
||||
msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler()
|
||||
keepers.WasmKeeper.messenger = msger
|
||||
*messenger = *msger
|
||||
|
||||
if spec.overwriteMessenger != nil {
|
||||
keepers.WasmKeeper.messenger = spec.overwriteMessenger
|
||||
*messenger = *spec.overwriteMessenger
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -419,7 +422,8 @@ func TestOnRecvPacket(t *testing.T) {
|
||||
func TestOnAckPacket(t *testing.T) {
|
||||
var m wasmtesting.MockWasmer
|
||||
wasmtesting.MakeIBCInstantiable(&m)
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures)
|
||||
var messenger = &wasmtesting.MockMessageHandler{}
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger))
|
||||
example := SeedNewContractInstance(t, parentCtx, keepers, &m)
|
||||
|
||||
specs := map[string]struct {
|
||||
@@ -427,7 +431,7 @@ func TestOnAckPacket(t *testing.T) {
|
||||
contractGas sdk.Gas
|
||||
contractResp *wasmvmtypes.IBCBasicResponse
|
||||
contractErr error
|
||||
overwriteMessenger Messenger
|
||||
overwriteMessenger *wasmtesting.MockMessageHandler
|
||||
expErr bool
|
||||
expContractEventAttrs int
|
||||
expNoEvents bool
|
||||
@@ -493,10 +497,10 @@ func TestOnAckPacket(t *testing.T) {
|
||||
defer cancel()
|
||||
before := ctx.GasMeter().GasConsumed()
|
||||
msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler()
|
||||
keepers.WasmKeeper.messenger = msger
|
||||
*messenger = *msger
|
||||
|
||||
if spec.overwriteMessenger != nil {
|
||||
keepers.WasmKeeper.messenger = spec.overwriteMessenger
|
||||
*messenger = *spec.overwriteMessenger
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -530,7 +534,8 @@ func TestOnAckPacket(t *testing.T) {
|
||||
func TestOnTimeoutPacket(t *testing.T) {
|
||||
var m wasmtesting.MockWasmer
|
||||
wasmtesting.MakeIBCInstantiable(&m)
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures)
|
||||
var messenger = &wasmtesting.MockMessageHandler{}
|
||||
parentCtx, keepers := CreateTestInput(t, false, SupportedFeatures, WithMessageHandler(messenger))
|
||||
example := SeedNewContractInstance(t, parentCtx, keepers, &m)
|
||||
|
||||
specs := map[string]struct {
|
||||
@@ -538,7 +543,7 @@ func TestOnTimeoutPacket(t *testing.T) {
|
||||
contractGas sdk.Gas
|
||||
contractResp *wasmvmtypes.IBCBasicResponse
|
||||
contractErr error
|
||||
overwriteMessenger Messenger
|
||||
overwriteMessenger *wasmtesting.MockMessageHandler
|
||||
expErr bool
|
||||
expContractEventAttrs int
|
||||
expNoEvents bool
|
||||
@@ -603,10 +608,10 @@ func TestOnTimeoutPacket(t *testing.T) {
|
||||
defer cancel()
|
||||
before := ctx.GasMeter().GasConsumed()
|
||||
msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler()
|
||||
keepers.WasmKeeper.messenger = msger
|
||||
*messenger = *msger
|
||||
|
||||
if spec.overwriteMessenger != nil {
|
||||
keepers.WasmKeeper.messenger = spec.overwriteMessenger
|
||||
*messenger = *spec.overwriteMessenger
|
||||
}
|
||||
|
||||
// when
|
||||
|
||||
25
x/wasm/keeper/wasmtesting/msg_dispatcher.go
Normal file
25
x/wasm/keeper/wasmtesting/msg_dispatcher.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package wasmtesting
|
||||
|
||||
import (
|
||||
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type MockMsgDispatcher struct {
|
||||
DispatchMessagesFn func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error
|
||||
DispatchSubmessagesFn func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error)
|
||||
}
|
||||
|
||||
func (m MockMsgDispatcher) DispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error {
|
||||
if m.DispatchMessagesFn == nil {
|
||||
panic("not expected to be called")
|
||||
}
|
||||
return m.DispatchMessagesFn(ctx, contractAddr, ibcPort, msgs)
|
||||
}
|
||||
|
||||
func (m MockMsgDispatcher) DispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
|
||||
if m.DispatchSubmessagesFn == nil {
|
||||
panic("not expected to be called")
|
||||
}
|
||||
return m.DispatchSubmessagesFn(ctx, contractAddr, ibcPort, msgs)
|
||||
}
|
||||
26
x/wasm/keeper/wasmtesting/store.go
Normal file
26
x/wasm/keeper/wasmtesting/store.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package wasmtesting
|
||||
|
||||
import (
|
||||
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// MockCommitMultiStore mock with a CacheMultiStore to capture commits
|
||||
type MockCommitMultiStore struct {
|
||||
sdk.CommitMultiStore
|
||||
Committed []bool
|
||||
}
|
||||
|
||||
func (m *MockCommitMultiStore) CacheMultiStore() storetypes.CacheMultiStore {
|
||||
m.Committed = append(m.Committed, false)
|
||||
return &mockCMS{m, &m.Committed[len(m.Committed)-1]}
|
||||
}
|
||||
|
||||
type mockCMS struct {
|
||||
sdk.CommitMultiStore
|
||||
committed *bool
|
||||
}
|
||||
|
||||
func (m *mockCMS) Write() {
|
||||
*m.committed = true
|
||||
}
|
||||
Reference in New Issue
Block a user