Skip to content

Commit 6e22706

Browse files
committed
feat: New IBCv2 msg handler
1 parent 31f0396 commit 6e22706

13 files changed

+110
-21
lines changed

app/app.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ func NewWasmApp(
593593
app.StakingKeeper,
594594
distrkeeper.NewQuerier(app.DistrKeeper),
595595
app.IBCKeeper.ChannelKeeper,
596+
app.IBCKeeper.ChannelKeeperV2,
596597
app.IBCKeeper.ChannelKeeper,
597598
app.TransferKeeper,
598599
app.MsgServiceRouter(),

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/CosmWasm/wasmd
33
go 1.23.6
44

55
require (
6-
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250311142732-2684ad434449
6+
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250328101044-d6dfa29f9935
77
github.com/cosmos/cosmos-proto v1.0.0-beta.5
88
github.com/cosmos/cosmos-sdk v0.50.12
99
github.com/cosmos/gogogateway v1.2.0 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
229229
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
230230
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250311142732-2684ad434449 h1:7dFOzheG+3HatTz/OmbPaudVCweJMz0dn2+8/eQPMcw=
231231
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250311142732-2684ad434449/go.mod h1:bMhLQL4Yp9CzJi9A83aR7VO9wockOsSlZbT4ztOl6bg=
232+
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250327151355-f5074f3c7b15 h1:mxr3yBBey5G3nbQ4iKnhGge92G0uZ8Z9roUJY31XI6I=
233+
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250327151355-f5074f3c7b15/go.mod h1:bMhLQL4Yp9CzJi9A83aR7VO9wockOsSlZbT4ztOl6bg=
234+
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250328082303-e8348960e65e h1:hFK2GkaUr03fPPENn6xf8cH2wa35wSvqDeKEJoulMfM=
235+
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250328082303-e8348960e65e/go.mod h1:bMhLQL4Yp9CzJi9A83aR7VO9wockOsSlZbT4ztOl6bg=
236+
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250328101044-d6dfa29f9935 h1:Vcn1k7PUiV1wyz+d1aEZEVQfbwzPO3B8DA6rnE5bhok=
237+
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250328101044-d6dfa29f9935/go.mod h1:bMhLQL4Yp9CzJi9A83aR7VO9wockOsSlZbT4ztOl6bg=
232238
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
233239
github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q=
234240
github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=

tests/e2e/ibc2_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package e2e_test
22

33
import (
4+
"encoding/json"
45
"testing"
56

67
ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
@@ -21,6 +22,14 @@ type QueryMsg struct {
2122
// ibc2 contract response type
2223
type State struct {
2324
IBC2PacketReceiveCounter uint32 `json:"ibc2_packet_receive_counter"`
25+
LastChannelID string `json:"last_channel_id"`
26+
LastPacketSeq uint64 `json:"last_packet_seq"`
27+
}
28+
29+
// Message sent to the ibc2 contract over IBCv2 channel
30+
type IbcPayload struct {
31+
ResponseWithoutAck bool `json:"response_without_ack"`
32+
SendAsyncAckForPrevMsg bool `json:"send_async_ack_for_prev_msg"`
2433
}
2534

2635
func TestIBC2ReceiveEntrypoint(t *testing.T) {
@@ -53,7 +62,10 @@ func TestIBC2ReceiveEntrypoint(t *testing.T) {
5362

5463
var err error
5564
timeoutTimestamp := chainA.GetTimeoutTimestampSecs()
56-
packet, err := path.EndpointB.MsgSendPacket(timeoutTimestamp, mockv2.NewMockPayload(contractPortB, contractPortA))
65+
payload := mockv2.NewMockPayload(contractPortB, contractPortA)
66+
payload.Value, err = json.Marshal(IbcPayload{ResponseWithoutAck: true})
67+
require.NoError(t, err)
68+
packet, err := path.EndpointB.MsgSendPacket(timeoutTimestamp, payload)
5769
require.NoError(t, err)
5870
err = path.EndpointA.MsgRecvPacket(packet)
5971
require.NoError(t, err)
@@ -62,4 +74,15 @@ func TestIBC2ReceiveEntrypoint(t *testing.T) {
6274
err = chainA.SmartQuery(contractAddrA.String(), QueryMsg{QueryState: struct{}{}}, &response)
6375
require.NoError(t, err)
6476
require.Equal(t, uint32(1), response.IBC2PacketReceiveCounter)
77+
78+
timeoutTimestamp = chainA.GetTimeoutTimestampSecs()
79+
payload = mockv2.NewMockPayload(contractPortB, contractPortA)
80+
payload.Value, err = json.Marshal(IbcPayload{SendAsyncAckForPrevMsg: true})
81+
require.NoError(t, err)
82+
packet, err = path.EndpointB.MsgSendPacket(timeoutTimestamp, payload)
83+
require.NoError(t, err)
84+
err = path.EndpointA.MsgRecvPacket(packet)
85+
require.NoError(t, err)
86+
87+
// TODO tkulik: We need https://github.com/CosmWasm/wasmd/issues/2171 in order to properly test receiving async ACKs
6588
}

tests/e2e/testdata/ibc2.wasm

66.2 KB
Binary file not shown.

x/wasm/keeper/genesis_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ func setupKeeper(t *testing.T) (*Keeper, sdk.Context) {
695695
nil,
696696
nil,
697697
nil,
698+
nil,
698699
tempDir,
699700
nodeConfig,
700701
wasmtypes.VMConfig{},

x/wasm/keeper/handler_plugin.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
1616

1717
"github.com/CosmWasm/wasmd/x/wasm/types"
18+
channelv2types "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"
1819
)
1920

2021
// msgEncoder is an extension point to customize encodings
@@ -40,6 +41,7 @@ func NewDefaultMessageHandler(
4041
keeper *Keeper,
4142
router MessageRouter,
4243
ics4Wrapper types.ICS4Wrapper,
44+
ics4v2Wrapper types.ICS4v2Wrapper,
4345
channelKeeper types.ChannelKeeper,
4446
bankKeeper types.Burner,
4547
cdc codec.Codec,
@@ -53,6 +55,7 @@ func NewDefaultMessageHandler(
5355
return NewMessageHandlerChain(
5456
NewSDKMessageHandler(cdc, router, encoders),
5557
NewIBCRawPacketHandler(ics4Wrapper, keeper, channelKeeper),
58+
NewIBC2RawPacketHandler(ics4v2Wrapper),
5659
NewBurnCoinMessageHandler(bankKeeper),
5760
)
5861
}
@@ -261,6 +264,58 @@ func (h IBCRawPacketHandler) DispatchMsg(ctx sdk.Context, _ sdk.AccAddress, cont
261264
}
262265
}
263266

267+
// IBC2RawPacketHandler handles IBC2Msg received from CosmWasm.
268+
type IBC2RawPacketHandler struct {
269+
ics4v2Wrapper types.ICS4v2Wrapper
270+
}
271+
272+
// NewIBCRawPacketHandler constructor
273+
func NewIBC2RawPacketHandler(ics4v2Wrapper types.ICS4v2Wrapper) IBC2RawPacketHandler {
274+
return IBC2RawPacketHandler{
275+
ics4v2Wrapper: ics4v2Wrapper,
276+
}
277+
}
278+
279+
// DispatchMsg publishes a raw IBC packet onto the channel.
280+
func (h IBC2RawPacketHandler) DispatchMsg(ctx sdk.Context, _ sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) {
281+
if msg.IBC2 == nil {
282+
return nil, nil, nil, types.ErrUnknownMsg
283+
}
284+
msgIBC2 := msg.IBC2
285+
switch {
286+
case msgIBC2.WriteAcknowledgement != nil:
287+
packet := msgIBC2.WriteAcknowledgement
288+
if contractIBCPortID == "" {
289+
return nil, nil, nil, errorsmod.Wrapf(types.ErrUnsupportedForContract, "ibc not supported")
290+
}
291+
contractIBCChannelID := msgIBC2.WriteAcknowledgement.ChannelID
292+
if contractIBCChannelID == "" {
293+
return nil, nil, nil, errorsmod.Wrapf(types.ErrEmpty, "ibc channel")
294+
}
295+
296+
err := h.ics4v2Wrapper.WriteAcknowledgement(ctx, packet.ChannelID, packet.PacketSequence, channelv2types.Acknowledgement{AppAcknowledgements: [][]byte{msgIBC2.WriteAcknowledgement.Ack.Data}})
297+
if err != nil {
298+
return nil, nil, nil, errorsmod.Wrap(err, "acknowledgement")
299+
}
300+
301+
resp := &types.MsgIBCWriteAcknowledgementResponse{}
302+
val, err := resp.Marshal()
303+
if err != nil {
304+
return nil, nil, nil, errorsmod.Wrap(err, "failed to marshal IBC send response")
305+
}
306+
307+
any, err := codectypes.NewAnyWithValue(resp)
308+
if err != nil {
309+
return nil, nil, nil, errorsmod.Wrap(err, "failed to convert IBC send response to Any")
310+
}
311+
msgResponses := [][]*codectypes.Any{{any}}
312+
313+
return nil, [][]byte{val}, msgResponses, nil
314+
default:
315+
return nil, nil, nil, types.ErrUnknownMsg
316+
}
317+
}
318+
264319
var _ Messenger = MessageHandlerFunc(nil)
265320

266321
// MessageHandlerFunc is a helper to construct a function based message handler.

x/wasm/keeper/ibc2.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (module IBC2Handler) OnRecvPacket(
5252
}
5353

5454
em := sdk.NewEventManager()
55-
msg := wasmvmtypes.IBC2PacketReceiveMsg{Payload: newIBC2Payload(payload), Relayer: relayer.String(), SourceClient: sourceClient}
55+
msg := wasmvmtypes.IBC2PacketReceiveMsg{Payload: newIBC2Payload(payload), Relayer: relayer.String(), SourceClient: sourceClient, PacketSequence: sequence}
5656

5757
ack := module.keeper.OnRecvIBC2Packet(ctx.WithEventManager(em), contractAddr, msg)
5858

@@ -133,7 +133,7 @@ func (k Keeper) OnRecvIBC2Packet(
133133
}
134134

135135
// note submessage reply results can overwrite the `Acknowledgement` data
136-
data, err := k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events)
136+
data, err := k.handleContractResponse(ctx, contractAddr, contractInfo.IBC2PortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events)
137137
if err != nil {
138138
// submessage errors result in error ACK with state reverted. Error message is redacted
139139
return channeltypesv2.RecvPacketResult{
@@ -143,16 +143,6 @@ func (k Keeper) OnRecvIBC2Packet(
143143
}
144144

145145
if data == nil {
146-
// In case of lack of ack, we assume that the packet should
147-
// be handled asynchronously.
148-
// TODO: https://github.com/CosmWasm/wasmd/issues/2161
149-
// err = k.StoreAsyncAckPacket(ctx, convertPacket(msg.Payload))
150-
// if err != nil {
151-
// return channeltypesv2.RecvPacketResult{
152-
// Status: channeltypesv2.PacketStatus_Failure,
153-
// Acknowledgement: []byte(err.Error()),
154-
// }
155-
// }
156146
return channeltypesv2.RecvPacketResult{
157147
Status: channeltypesv2.PacketStatus_Async,
158148
}

x/wasm/keeper/keeper_cgo.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func NewKeeper(
2727
stakingKeeper types.StakingKeeper,
2828
distrKeeper types.DistributionKeeper,
2929
ics4Wrapper types.ICS4Wrapper,
30+
ics4v2Wrapper types.ICS4v2Wrapper,
3031
channelKeeper types.ChannelKeeper,
3132
portSource types.ICS20TransferPortSource,
3233
router MessageRouter,
@@ -60,7 +61,7 @@ func NewKeeper(
6061
wasmLimits: vmConfig.WasmLimits,
6162
ibcRouterV2: ibcRouterV2,
6263
}
63-
keeper.messenger = NewDefaultMessageHandler(keeper, router, ics4Wrapper, channelKeeper, bankKeeper, cdc, portSource)
64+
keeper.messenger = NewDefaultMessageHandler(keeper, router, ics4Wrapper, ics4v2Wrapper, channelKeeper, bankKeeper, cdc, portSource)
6465
keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distrKeeper, channelKeeper, keeper)
6566
preOpts, postOpts := splitOpts(opts)
6667
for _, o := range preOpts {

x/wasm/keeper/keeper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ func TestInstantiateWithContractFactoryChildQueriesParent(t *testing.T) {
848848
router := baseapp.NewMsgServiceRouter()
849849
router.SetInterfaceRegistry(keepers.EncodingConfig.InterfaceRegistry)
850850
types.RegisterMsgServer(router, NewMsgServerImpl(keeper))
851-
keeper.messenger = NewDefaultMessageHandler(nil, router, nil, nil, nil, keepers.EncodingConfig.Codec, nil)
851+
keeper.messenger = NewDefaultMessageHandler(nil, router, nil, nil, nil, nil, keepers.EncodingConfig.Codec, nil)
852852
// overwrite wasmvm in response handler
853853
keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper))
854854

x/wasm/keeper/options_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func TestConstructorOptions(t *testing.T) {
155155
opt := spec.srcOpt
156156
_, gotPostOptMarker := opt.(postOptsFn)
157157
require.Equal(t, spec.isPostOpt, gotPostOptMarker)
158-
k := NewKeeper(codec, runtime.NewKVStoreService(storeKey), authkeeper.AccountKeeper{}, &bankkeeper.BaseKeeper{}, stakingkeeper.Keeper{}, nil, nil, nil, nil, nil, nil, tempDir, types.DefaultNodeConfig(), types.VMConfig{}, AvailableCapabilities, "", nil, spec.srcOpt)
158+
k := NewKeeper(codec, runtime.NewKVStoreService(storeKey), authkeeper.AccountKeeper{}, &bankkeeper.BaseKeeper{}, stakingkeeper.Keeper{}, nil, nil, nil, nil, nil, nil, nil, tempDir, types.DefaultNodeConfig(), types.VMConfig{}, AvailableCapabilities, "", nil, spec.srcOpt)
159159
spec.verify(t, k)
160160
})
161161
}

x/wasm/keeper/test_common.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,8 @@ func createTestInput(
377377
bankKeeper,
378378
stakingKeeper,
379379
distributionkeeper.NewQuerier(distKeeper),
380-
ibcKeeper.ChannelKeeper, // ICS4Wrapper
380+
ibcKeeper.ChannelKeeper, // ICS4Wrapper
381+
ibcKeeper.ChannelKeeperV2, // ICS4WrapperV2
381382
ibcKeeper.ChannelKeeper,
382383
wasmtesting.MockIBCTransferKeeper{},
383384
msgRouter,

x/wasm/types/expected_keepers.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
1313
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
1414
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
15+
channelv2types "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"
1516
)
1617

1718
// BankViewKeeper defines a subset of methods implemented by the cosmos-sdk bank keeper
@@ -88,9 +89,6 @@ type ChannelKeeper interface {
8889
// The interface is implemented by the channel keeper on the lowest level in ibc-go. Middlewares or other abstractions
8990
// can add functionality on top of it. See ics4Wrapper in ibc-go.
9091
// It is important to choose the right implementation that is configured for any middleware used in the ibc-stack of wasm.
91-
//
92-
// For example, when ics-29 fee middleware is set up for the wasm ibc-stack, then the IBCFeeKeeper should be used, so
93-
// that they are in sync.
9492
type ICS4Wrapper interface {
9593
// SendPacket is called by a module in order to send an IBC packet on a channel.
9694
// The packet sequence generated for the packet to be sent is returned. An error
@@ -111,6 +109,19 @@ type ICS4Wrapper interface {
111109
) error
112110
}
113111

112+
// ICS4Wrapper defines the method for an IBC data package to be submitted.
113+
// The interface is implemented by the channel keeper on the lowest level in ibc-go. Middlewares or other abstractions
114+
// can add functionality on top of it. See ics4Wrapper in ibc-go.
115+
// It is important to choose the right implementation that is configured for any middleware used in the ibc-stack of wasm.
116+
type ICS4v2Wrapper interface {
117+
WriteAcknowledgement(
118+
ctx sdk.Context,
119+
clientID string,
120+
sequence uint64,
121+
ack channelv2types.Acknowledgement,
122+
) error
123+
}
124+
114125
// ClientKeeper defines the expected IBC client keeper
115126
type ClientKeeper interface {
116127
GetClientConsensusState(ctx sdk.Context, clientID string) (connection ibcexported.ConsensusState, found bool)

0 commit comments

Comments
 (0)