Skip to content

Commit 867de24

Browse files
authored
Merge pull request #2174 from CosmWasm/jawoznia/test/ibc2-send-msg
Add E2E test for IBC2SendMsg (#2165)
2 parents d86eb34 + adc8e58 commit 867de24

File tree

12 files changed

+257
-15
lines changed

12 files changed

+257
-15
lines changed

app/app.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ func NewWasmApp(
594594
distrkeeper.NewQuerier(app.DistrKeeper),
595595
app.IBCKeeper.ChannelKeeper,
596596
app.IBCKeeper.ChannelKeeper,
597+
app.IBCKeeper.ChannelKeeperV2,
597598
app.TransferKeeper,
598599
app.MsgServiceRouter(),
599600
app.GRPCQueryRouter(),

tests/e2e/ibc2_test.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package e2e_test
22

33
import (
44
"testing"
5+
"time"
56

67
ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
78
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
@@ -23,19 +24,21 @@ type State struct {
2324
IBC2PacketReceiveCounter uint32 `json:"ibc2_packet_receive_counter"`
2425
}
2526

26-
func TestIBC2ReceiveEntrypoint(t *testing.T) {
27+
func TestIBC2SendMsg(t *testing.T) {
2728
coord := wasmibctesting.NewCoordinator(t, 2)
2829
chainA := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(1)))
2930
chainB := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(2)))
30-
3131
contractCodeA := chainA.StoreCodeFile("./testdata/ibc2.wasm").CodeID
3232
contractAddrA := chainA.InstantiateContract(contractCodeA, []byte(`{}`))
3333
contractPortA := wasmkeeper.PortIDForContractV2(contractAddrA)
34+
require.NotEmpty(t, contractAddrA)
3435

3536
contractCodeB := chainB.StoreCodeFile("./testdata/ibc2.wasm").CodeID
37+
// Skip initial contract address to not overlap with ChainA
38+
_ = chainB.InstantiateContract(contractCodeB, []byte(`{}`))
3639
contractAddrB := chainB.InstantiateContract(contractCodeB, []byte(`{}`))
3740
contractPortB := wasmkeeper.PortIDForContractV2(contractAddrB)
38-
require.NotEmpty(t, contractAddrA)
41+
require.NotEmpty(t, contractAddrB)
3942

4043
path := wasmibctesting.NewWasmPath(chainA, chainB)
4144
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
@@ -51,15 +54,42 @@ func TestIBC2ReceiveEntrypoint(t *testing.T) {
5154

5255
path.Path.SetupV2()
5356

54-
var err error
55-
timeoutTimestamp := chainA.GetTimeoutTimestampSecs()
56-
packet, err := path.EndpointB.MsgSendPacket(timeoutTimestamp, mockv2.NewMockPayload(contractPortB, contractPortA))
57+
// IBC v2 Payload from contract on Chain B to contract on Chain A
58+
payload := mockv2.NewMockPayload(contractPortB, contractPortA)
59+
60+
// Message timeout
61+
timeoutTimestamp := uint64(chainB.GetContext().BlockTime().Add(time.Minute * 5).Unix())
62+
63+
_, err := path.EndpointB.MsgSendPacket(timeoutTimestamp, payload)
5764
require.NoError(t, err)
58-
err = path.EndpointA.MsgRecvPacket(packet)
65+
66+
// First message send through test
67+
err = wasmibctesting.RelayPendingPacketsV2(path)
5968
require.NoError(t, err)
6069

70+
// Check if counter was incremented in the recv entry point
6171
var response State
72+
6273
err = chainA.SmartQuery(contractAddrA.String(), QueryMsg{QueryState: struct{}{}}, &response)
6374
require.NoError(t, err)
6475
require.Equal(t, uint32(1), response.IBC2PacketReceiveCounter)
76+
77+
// The counters on both Chains are both incremented in every iteration of the loop,
78+
// because once the first relaying loop in `RelayPendingPacketsV2` the array of
79+
// pending packets on the other chain is updated with new packet send from the contract.
80+
for i := 1; i <= 100; i++ {
81+
// Relay message sent by contract
82+
err = wasmibctesting.RelayPendingPacketsV2(path)
83+
require.NoError(t, err)
84+
85+
// Check counter in contract A
86+
err = chainA.SmartQuery(contractAddrA.String(), QueryMsg{QueryState: struct{}{}}, &response)
87+
require.NoError(t, err)
88+
require.Equal(t, uint32(i+1), response.IBC2PacketReceiveCounter)
89+
90+
// Check counter in contract B
91+
err = chainB.SmartQuery(contractAddrB.String(), QueryMsg{QueryState: struct{}{}}, &response)
92+
require.NoError(t, err)
93+
require.Equal(t, uint32(i), response.IBC2PacketReceiveCounter)
94+
}
6595
}

tests/e2e/testdata/ibc2.wasm

100644100755
64.8 KB
Binary file not shown.

tests/wasmibctesting/utils.go

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/cosmos/gogoproto/proto"
2020
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
2121
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
22+
channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"
2223
host "github.com/cosmos/ibc-go/v10/modules/core/24-host"
2324
ibctesting "github.com/cosmos/ibc-go/v10/testing"
2425
"github.com/stretchr/testify/require"
@@ -55,21 +56,82 @@ func (app WasmTestApp) GetTxConfig() client.TxConfig {
5556
type WasmTestChain struct {
5657
*ibctesting.TestChain
5758

58-
PendingSendPackets *[]channeltypes.Packet
59+
PendingSendPackets *[]channeltypes.Packet
60+
PendingSendPacketsV2 *[]channeltypesv2.Packet
5961
}
6062

6163
func NewWasmTestChain(chain *ibctesting.TestChain) *WasmTestChain {
62-
res := WasmTestChain{TestChain: chain, PendingSendPackets: &[]channeltypes.Packet{}}
64+
res := WasmTestChain{TestChain: chain, PendingSendPackets: &[]channeltypes.Packet{}, PendingSendPacketsV2: &[]channeltypesv2.Packet{}}
6365
res.SendMsgsOverride = res.OverrideSendMsgs
6466
return &res
6567
}
6668

69+
func (chain *WasmTestChain) CaptureIBCEventsV2(result *abci.ExecTxResult) {
70+
toSend, err := ParsePacketsFromEventsV2(channeltypesv2.EventTypeSendPacket, result.Events)
71+
require.NoError(chain, err)
72+
if len(toSend) > 0 {
73+
// Keep a queue on the chain that we can relay in tests
74+
*chain.PendingSendPacketsV2 = append(*chain.PendingSendPacketsV2, toSend...)
75+
}
76+
}
77+
78+
// TODO: Remove this once it's implemented in the `ibc-go`.
79+
// https://github.com/cosmos/ibc-go/issues/8284
80+
//
81+
// ParsePacketsFromEventsV2 parses events emitted from a MsgRecvPacket and returns
82+
// all the packets found.
83+
// Returns an error if no packet is found.
84+
func ParsePacketsFromEventsV2(eventType string, events []abci.Event) ([]channeltypesv2.Packet, error) {
85+
ferr := func(err error) ([]channeltypesv2.Packet, error) {
86+
return nil, fmt.Errorf("wasmd.ParsePacketsFromEventsV2: %w", err)
87+
}
88+
var packets []channeltypesv2.Packet
89+
for _, ev := range events {
90+
if ev.Type == eventType {
91+
for _, attr := range ev.Attributes {
92+
switch attr.Key {
93+
case channeltypesv2.AttributeKeyEncodedPacketHex:
94+
data, err := hex.DecodeString(attr.Value)
95+
if err != nil {
96+
return ferr(err)
97+
}
98+
var packet channeltypesv2.Packet
99+
err = proto.Unmarshal(data, &packet)
100+
if err != nil {
101+
return ferr(err)
102+
}
103+
packets = append(packets, packet)
104+
105+
default:
106+
continue
107+
}
108+
}
109+
}
110+
}
111+
return packets, nil
112+
}
113+
67114
func (chain *WasmTestChain) CaptureIBCEvents(result *abci.ExecTxResult) {
68115
toSend, _ := ibctesting.ParsePacketsFromEvents(channeltypes.EventTypeSendPacket, result.Events)
116+
117+
// IBCv1 and IBCv2 `EventTypeSendPacket` are the same
118+
// and the [`ParsePacketsFromEvents`] parses both of them as they were IBCv1
119+
// so we have to filter them here.
120+
//
121+
// While parsing IBC2 events in IBC1 context the only overlapping event is the
122+
// `AttributeKeyTimeoutTimestamp` so to determine if the wrong set of events was parsed
123+
// we should be able to check if any other field in the packet is not set.
124+
var toSendFiltered []channeltypes.Packet
125+
for _, packet := range toSend {
126+
if packet.SourcePort != "" {
127+
toSendFiltered = append(toSendFiltered, packet)
128+
}
129+
}
130+
69131
// require.NoError(chain, err)
70-
if len(toSend) > 0 {
132+
if len(toSendFiltered) > 0 {
71133
// Keep a queue on the chain that we can relay in tests
72-
*chain.PendingSendPackets = append(*chain.PendingSendPackets, toSend...)
134+
*chain.PendingSendPackets = append(*chain.PendingSendPackets, toSendFiltered...)
73135
}
74136
}
75137

@@ -78,6 +140,7 @@ func (chain *WasmTestChain) OverrideSendMsgs(msgs ...sdk.Msg) (*abci.ExecTxResul
78140
result, err := chain.TestChain.SendMsgs(msgs...)
79141
chain.SendMsgsOverride = chain.OverrideSendMsgs
80142
chain.CaptureIBCEvents(result)
143+
chain.CaptureIBCEventsV2(result)
81144
return result, err
82145
}
83146

@@ -297,6 +360,45 @@ func RelayPacketWithoutAck(path *ibctesting.Path, packet channeltypes.Packet) er
297360
return fmt.Errorf("packet commitment does not exist on either endpoint for provided packet")
298361
}
299362

363+
// RelayPacketWithoutAckV2 attempts to relay the packet first on EndpointA and then on EndpointB
364+
// if EndpointA does not contain a packet commitment for that packet. An error is returned
365+
// if a relay step fails or the packet commitment does not exist on either endpoint.
366+
// In contrast to RelayPacket, this function does not acknowledge the packet and expects it to have no acknowledgement yet.
367+
// It is useful for testing async acknowledgement.
368+
func RelayPacketWithoutAckV2(path *WasmPath, packet channeltypesv2.Packet) error {
369+
pc := path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(path.EndpointA.Chain.GetContext(), packet.GetSourceClient(), packet.GetSequence())
370+
if bytes.Equal(pc, channeltypesv2.CommitPacket(packet)) {
371+
// packet found, relay from A to B
372+
if err := path.EndpointB.UpdateClient(); err != nil {
373+
return err
374+
}
375+
376+
err := path.EndpointB.MsgRecvPacket(packet)
377+
if err != nil {
378+
return err
379+
}
380+
381+
return nil
382+
}
383+
384+
pc = path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(path.EndpointB.Chain.GetContext(), packet.GetSourceClient(), packet.GetSequence())
385+
if bytes.Equal(pc, channeltypesv2.CommitPacket(packet)) {
386+
// packet found, relay B to A
387+
if err := path.EndpointA.UpdateClient(); err != nil {
388+
return err
389+
}
390+
391+
err := path.EndpointA.MsgRecvPacket(packet)
392+
if err != nil {
393+
return err
394+
}
395+
396+
return nil
397+
}
398+
399+
return fmt.Errorf("packet commitment does not exist on either endpointV2 for provided packet")
400+
}
401+
300402
type WasmPath struct {
301403
ibctesting.Path
302404

@@ -338,6 +440,34 @@ func RelayAndAckPendingPackets(path *WasmPath) error {
338440
return nil
339441
}
340442

443+
// RelayAndAckPendingPackets sends pending packages from path.EndpointA to the counterparty chain and acks
444+
func RelayPendingPacketsV2(path *WasmPath) error {
445+
// get all the packet to relay src->dest
446+
src := path.EndpointA
447+
require.NoError(path.chainA, src.UpdateClient())
448+
path.chainA.Logf("Relay: %d PacketsV2 A->B, %d PacketsV2 B->A\n", len(*path.chainA.PendingSendPacketsV2), len(*path.chainB.PendingSendPacketsV2))
449+
for _, v := range *path.chainA.PendingSendPacketsV2 {
450+
err := RelayPacketWithoutAckV2(path, v)
451+
if err != nil {
452+
return err
453+
}
454+
455+
*path.chainA.PendingSendPacketsV2 = (*path.chainA.PendingSendPacketsV2)[1:]
456+
}
457+
458+
src = path.EndpointB
459+
require.NoError(path.chainB, src.UpdateClient())
460+
for _, v := range *path.chainB.PendingSendPacketsV2 {
461+
err := RelayPacketWithoutAckV2(path, v)
462+
if err != nil {
463+
return err
464+
}
465+
466+
*path.chainB.PendingSendPacketsV2 = (*path.chainB.PendingSendPacketsV2)[1:]
467+
}
468+
return nil
469+
}
470+
341471
// TimeoutPendingPackets returns the package to source chain to let the IBC app revert any operation.
342472
// from A to B
343473
func TimeoutPendingPackets(coord *ibctesting.Coordinator, path *WasmPath) error {

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: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
8+
channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"
89

910
errorsmod "cosmossdk.io/errors"
1011

@@ -40,6 +41,7 @@ func NewDefaultMessageHandler(
4041
keeper *Keeper,
4142
router MessageRouter,
4243
ics4Wrapper types.ICS4Wrapper,
44+
channelKeeperV2 types.ChannelKeeperV2,
4345
bankKeeper types.Burner,
4446
cdc codec.Codec,
4547
portSource types.ICS20TransferPortSource,
@@ -52,6 +54,7 @@ func NewDefaultMessageHandler(
5254
return NewMessageHandlerChain(
5355
NewSDKMessageHandler(cdc, router, encoders),
5456
NewIBCRawPacketHandler(ics4Wrapper, keeper),
57+
NewIBC2RawPacketHandler(channelKeeperV2),
5558
NewBurnCoinMessageHandler(bankKeeper),
5659
)
5760
}
@@ -258,6 +261,75 @@ func (h IBCRawPacketHandler) DispatchMsg(ctx sdk.Context, _ sdk.AccAddress, cont
258261
}
259262
}
260263

264+
// IBC2RawPacketHandler handles IBC2Msg received from CosmWasm.
265+
type IBC2RawPacketHandler struct {
266+
channelKeeperV2 types.ChannelKeeperV2
267+
}
268+
269+
// NewIBCRawPacketHandler constructor
270+
func NewIBC2RawPacketHandler(channelKeeperV2 types.ChannelKeeperV2) IBC2RawPacketHandler {
271+
return IBC2RawPacketHandler{
272+
channelKeeperV2: channelKeeperV2,
273+
}
274+
}
275+
276+
// DispatchMsg publishes a raw IBC2 packet onto the channel.
277+
func (h IBC2RawPacketHandler) DispatchMsg(ctx sdk.Context,
278+
contractAddr sdk.AccAddress, contractIBC2PortID string, msg wasmvmtypes.CosmosMsg,
279+
) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) {
280+
if msg.IBC2 == nil {
281+
return nil, nil, nil, types.ErrUnknownMsg
282+
}
283+
switch {
284+
case msg.IBC2.SendPacket != nil:
285+
if contractIBC2PortID == "" {
286+
return nil, nil, nil, errorsmod.Wrapf(types.ErrUnsupportedForContract, "ibc2 not supported")
287+
}
288+
contractIBCChannelID := msg.IBC2.SendPacket.ChannelID
289+
if contractIBCChannelID == "" {
290+
return nil, nil, nil, errorsmod.Wrapf(types.ErrEmpty, "ibc2 channel")
291+
}
292+
293+
var payloads []channeltypesv2.Payload
294+
for _, payload := range msg.IBC2.SendPacket.Payloads {
295+
payloads = append(payloads, channeltypesv2.Payload{
296+
SourcePort: payload.SourcePort,
297+
DestinationPort: payload.DestinationPort,
298+
Version: payload.Version,
299+
Encoding: payload.Encoding,
300+
Value: payload.Value,
301+
})
302+
}
303+
ibcGoMsg := &channeltypesv2.MsgSendPacket{
304+
SourceClient: msg.IBC2.SendPacket.ChannelID,
305+
TimeoutTimestamp: msg.IBC2.SendPacket.Timeout,
306+
Payloads: payloads,
307+
Signer: contractAddr.String(),
308+
}
309+
310+
ibcGoResp, err := h.channelKeeperV2.SendPacket(ctx, ibcGoMsg)
311+
if err != nil {
312+
return nil, nil, nil, errorsmod.Wrap(err, "channel")
313+
}
314+
moduleLogger(ctx).Debug("ibc2 packet set", "seq", ibcGoResp.Sequence)
315+
316+
resp := &types.MsgIBCSendResponse{Sequence: ibcGoResp.Sequence}
317+
val, err := resp.Marshal()
318+
if err != nil {
319+
return nil, nil, nil, errorsmod.Wrap(err, "failed to marshal IBC2 send response")
320+
}
321+
any, err := codectypes.NewAnyWithValue(resp)
322+
if err != nil {
323+
return nil, nil, nil, errorsmod.Wrap(err, "failed to convert IBC2 send response to Any")
324+
}
325+
msgResponses := [][]*codectypes.Any{{any}}
326+
327+
return nil, [][]byte{val}, msgResponses, nil
328+
default:
329+
return nil, nil, nil, types.ErrUnknownMsg
330+
}
331+
}
332+
261333
var _ Messenger = MessageHandlerFunc(nil)
262334

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

x/wasm/keeper/ibc2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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{

0 commit comments

Comments
 (0)