package keeper import ( "errors" "fmt" wasmvmtypes "github.com/CosmWasm/wasmvm/v3/types" channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/CosmWasm/wasmd/x/wasm/types" ) // msgEncoder is an extension point to customize encodings type msgEncoder interface { // Encode converts wasmvm message to n cosmos message types Encode(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Msg, error) } // MessageRouter ADR 031 request type routing type MessageRouter interface { Handler(msg sdk.Msg) baseapp.MsgServiceHandler } // SDKMessageHandler can handles messages that can be encoded into sdk.Message types and routed. type SDKMessageHandler struct { router MessageRouter encoders msgEncoder cdc codec.Codec } // NewDefaultMessageHandler constructor func NewDefaultMessageHandler( keeper *Keeper, router MessageRouter, ics4Wrapper types.ICS4Wrapper, channelKeeperV2 types.ChannelKeeperV2, bankKeeper types.Burner, cdc codec.Codec, portSource types.ICS20TransferPortSource, customEncoders ...*MessageEncoders, ) Messenger { encoders := DefaultEncoders(cdc, portSource) for _, e := range customEncoders { encoders = encoders.Merge(e) } return NewMessageHandlerChain( NewSDKMessageHandler(cdc, router, encoders), NewIBCRawPacketHandler(ics4Wrapper, keeper), NewIBC2RawPacketHandler(channelKeeperV2), NewBurnCoinMessageHandler(bankKeeper), ) } func NewSDKMessageHandler(cdc codec.Codec, router MessageRouter, encoders msgEncoder) SDKMessageHandler { return SDKMessageHandler{ cdc: cdc, router: router, encoders: encoders, } } func (h SDKMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, msgResponses [][]*codectypes.Any, err error) { sdkMsgs, err := h.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) if err != nil { return nil, nil, nil, err } for _, sdkMsg := range sdkMsgs { res, err := h.handleSdkMessage(ctx, contractAddr, sdkMsg) if err != nil { return nil, nil, nil, err } // append data and msgResponses data = append(data, res.Data) msgResponses = append(msgResponses, res.MsgResponses) // 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 SDKMessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) { // todo: this block needs proper review from sdk team if m, ok := msg.(sdk.HasValidateBasic); ok { if err := m.ValidateBasic(); err != nil { return nil, err } } // make sure this account can send it signers, _, err := h.cdc.GetMsgV1Signers(msg) if err != nil { return nil, err } for _, acct := range signers { if !contractAddr.Equals(sdk.AccAddress(acct)) { return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") } } // --- end block // find the handler and execute it if handler := h.router.Handler(msg); handler != nil { // ADR 031 request type routing msgResult, err := handler(ctx, msg) return msgResult, err } // legacy sdk.Msg routing // Assuming that the app developer has migrated all their Msgs to // proto messages and has registered all `Msg services`, then this // path should never be called, because all those Msgs should be // registered within the `msgServiceRouter` already. return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) } type callDepthMessageHandler struct { Messenger MaxCallDepth uint32 } func (h callDepthMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, msgResponses [][]*codectypes.Any, err error) { ctx, err = checkAndIncreaseCallDepth(ctx, h.MaxCallDepth) if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "dispatch") } return h.Messenger.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } // MessageHandlerChain defines a chain of handlers that are called one by one until it can be handled. type MessageHandlerChain struct { handlers []Messenger } func NewMessageHandlerChain(first Messenger, others ...Messenger) *MessageHandlerChain { r := &MessageHandlerChain{handlers: append([]Messenger{first}, others...)} for i := range r.handlers { if r.handlers[i] == nil { panic(fmt.Sprintf("handler must not be nil at position : %d", i)) } } return r } // DispatchMsg dispatch message and calls chained handlers one after another in // order to find the right one to process given message. If a handler cannot // process given message (returns ErrUnknownMsg), its result is ignored and the // next handler is executed. func (m MessageHandlerChain) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) { for _, h := range m.handlers { events, data, msgResponses, err := h.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) switch { case err == nil: return events, data, msgResponses, nil case errors.Is(err, types.ErrUnknownMsg): continue default: return events, data, msgResponses, err } } return nil, nil, nil, errorsmod.Wrap(types.ErrUnknownMsg, "no handler found") } // IBCRawPacketHandler handles IBC.SendPacket messages which are published to an IBC channel. type IBCRawPacketHandler struct { ics4Wrapper types.ICS4Wrapper wasmKeeper types.IBCContractKeeper } // NewIBCRawPacketHandler constructor func NewIBCRawPacketHandler(ics4Wrapper types.ICS4Wrapper, wasmKeeper types.IBCContractKeeper) IBCRawPacketHandler { return IBCRawPacketHandler{ ics4Wrapper: ics4Wrapper, wasmKeeper: wasmKeeper, } } // DispatchMsg publishes a raw IBC packet onto the channel. func (h IBCRawPacketHandler) DispatchMsg(ctx sdk.Context, _ sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) { if msg.IBC == nil { return nil, nil, nil, types.ErrUnknownMsg } switch { case msg.IBC.SendPacket != nil: if contractIBCPortID == "" { return nil, nil, nil, errorsmod.Wrapf(types.ErrUnsupportedForContract, "ibc not supported") } contractIBCChannelID := msg.IBC.SendPacket.ChannelID if contractIBCChannelID == "" { return nil, nil, nil, errorsmod.Wrapf(types.ErrEmpty, "ibc channel") } seq, err := h.ics4Wrapper.SendPacket(ctx, contractIBCPortID, contractIBCChannelID, ConvertWasmIBCTimeoutHeightToCosmosHeight(msg.IBC.SendPacket.Timeout.Block), msg.IBC.SendPacket.Timeout.Timestamp, msg.IBC.SendPacket.Data) if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "channel") } moduleLogger(ctx).Debug("ibc packet set", "seq", seq) resp := &types.MsgIBCSendResponse{Sequence: seq} val, err := resp.Marshal() if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "failed to marshal IBC send response") } any, err := codectypes.NewAnyWithValue(resp) if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "failed to convert IBC send response to Any") } msgResponses := [][]*codectypes.Any{{any}} return nil, [][]byte{val}, msgResponses, nil case msg.IBC.WriteAcknowledgement != nil: if contractIBCPortID == "" { return nil, nil, nil, errorsmod.Wrapf(types.ErrUnsupportedForContract, "ibc not supported") } contractIBCChannelID := msg.IBC.WriteAcknowledgement.ChannelID if contractIBCChannelID == "" { return nil, nil, nil, errorsmod.Wrapf(types.ErrEmpty, "ibc channel") } packet, err := h.wasmKeeper.LoadAsyncAckPacket(ctx, contractIBCPortID, contractIBCChannelID, msg.IBC.WriteAcknowledgement.PacketSequence) if err != nil { return nil, nil, nil, errorsmod.Wrap(types.ErrInvalid, "packet") } err = h.ics4Wrapper.WriteAcknowledgement(ctx, packet, ContractConfirmStateAck(msg.IBC.WriteAcknowledgement.Ack.Data)) if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "acknowledgement") } // Delete the packet from the store after acknowledgement. // This ensures WriteAcknowledgement can only be used once per packet // such that overriding the acknowledgement later on is not possible. h.wasmKeeper.DeleteAsyncAckPacket(ctx, contractIBCPortID, contractIBCChannelID, msg.IBC.WriteAcknowledgement.PacketSequence) resp := &types.MsgIBCWriteAcknowledgementResponse{} val, err := resp.Marshal() if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "failed to marshal IBC send response") } any, err := codectypes.NewAnyWithValue(resp) if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "failed to convert IBC send response to Any") } msgResponses := [][]*codectypes.Any{{any}} return nil, [][]byte{val}, msgResponses, nil default: return nil, nil, nil, types.ErrUnknownMsg } } // IBC2RawPacketHandler handles IBC2Msg received from CosmWasm. type IBC2RawPacketHandler struct { channelKeeperV2 types.ChannelKeeperV2 } // NewIBCRawPacketHandler constructor func NewIBC2RawPacketHandler(channelKeeperV2 types.ChannelKeeperV2) IBC2RawPacketHandler { return IBC2RawPacketHandler{ channelKeeperV2: channelKeeperV2, } } // DispatchMsg publishes a raw IBC2 packet onto the channel. func (h IBC2RawPacketHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBC2PortID string, msg wasmvmtypes.CosmosMsg, ) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) { if msg.IBC2 == nil { return nil, nil, nil, types.ErrUnknownMsg } switch { case msg.IBC2.WriteAcknowledgement != nil: packet := msg.IBC2.WriteAcknowledgement if contractIBC2PortID == "" { return nil, nil, nil, errorsmod.Wrapf(types.ErrUnsupportedForContract, "ibc2 not supported") } sourceClient := msg.IBC2.WriteAcknowledgement.SourceClient if sourceClient == "" { return nil, nil, nil, errorsmod.Wrapf(types.ErrEmpty, "ibc2 channel") } err := h.channelKeeperV2.WriteAcknowledgement( ctx, packet.SourceClient, packet.PacketSequence, channeltypesv2.Acknowledgement{AppAcknowledgements: [][]byte{msg.IBC2.WriteAcknowledgement.Ack.Data}}, ) if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "ibc2 Write Acknowledgement") } resp := &types.MsgIBCWriteAcknowledgementResponse{} val, err := resp.Marshal() if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "failed to marshal IBC2 Write Acknowledgement") } any, err := codectypes.NewAnyWithValue(resp) if err != nil { return nil, nil, nil, errorsmod.Wrap(err, "failed to convert IBC2 Write Acknowledgement to Any") } msgResponses := [][]*codectypes.Any{{any}} return nil, [][]byte{val}, msgResponses, nil default: return nil, nil, nil, types.ErrUnknownMsg } } var _ Messenger = MessageHandlerFunc(nil) // MessageHandlerFunc is a helper to construct a function based message handler. type MessageHandlerFunc func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, msgResponses [][]*codectypes.Any, err error) // DispatchMsg delegates dispatching of provided message into the MessageHandlerFunc. func (m MessageHandlerFunc) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, msgResponses [][]*codectypes.Any, err error) { return m(ctx, contractAddr, contractIBCPortID, msg) } // NewBurnCoinMessageHandler handles wasmvm.BurnMsg messages func NewBurnCoinMessageHandler(burner types.Burner) MessageHandlerFunc { return func(ctx sdk.Context, contractAddr sdk.AccAddress, _ string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, msgResponses [][]*codectypes.Any, err error) { if msg.Bank != nil && msg.Bank.Burn != nil { coins, err := ConvertWasmCoinsToSdkCoins(msg.Bank.Burn.Amount) if err != nil { return nil, nil, nil, err } if coins.IsZero() { return nil, nil, nil, types.ErrEmpty.Wrap("amount") } if err := burner.SendCoinsFromAccountToModule(ctx, contractAddr, types.ModuleName, coins); err != nil { return nil, nil, nil, errorsmod.Wrap(err, "transfer to module") } if err := burner.BurnCoins(ctx, types.ModuleName, coins); err != nil { return nil, nil, nil, errorsmod.Wrap(err, "burn coins") } moduleLogger(ctx).Info("Burned", "amount", coins) return nil, nil, nil, nil } return nil, nil, nil, types.ErrUnknownMsg } }