Skip to content

Commit 9f677ba

Browse files
MsgTransfer For Eureka (#7957)
* initial progress * progress * initial build and existing tests pass * testing * documentation for tests * refactor * fix regression * Update modules/apps/transfer/types/packet.go Co-authored-by: Gjermund Garaba <[email protected]> * remove clientKeeperv2 necessity in code * remove clientkeeperv2 in transfer keeper --------- Co-authored-by: Gjermund Garaba <[email protected]>
1 parent 0e8a6c9 commit 9f677ba

File tree

19 files changed

+419
-133
lines changed

19 files changed

+419
-133
lines changed

modules/apps/callbacks/testing/simapp/app.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ func NewSimApp(
390390
appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName),
391391
app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware
392392
app.IBCKeeper.ChannelKeeper,
393+
app.IBCKeeper.ChannelKeeperV2,
393394
app.AccountKeeper, app.BankKeeper,
394395
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
395396
)

modules/apps/transfer/keeper/keeper.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ type Keeper struct {
2929
cdc codec.BinaryCodec
3030
legacySubspace types.ParamSubspace
3131

32-
ics4Wrapper porttypes.ICS4Wrapper
33-
channelKeeper types.ChannelKeeper
34-
AuthKeeper types.AccountKeeper
35-
BankKeeper types.BankKeeper
32+
ics4Wrapper porttypes.ICS4Wrapper
33+
channelKeeper types.ChannelKeeper
34+
channelKeeperV2 types.ChannelKeeperV2
35+
AuthKeeper types.AccountKeeper
36+
BankKeeper types.BankKeeper
3637

3738
// the address capable of executing a MsgUpdateParams message. Typically, this
3839
// should be the x/gov module account.
@@ -46,6 +47,7 @@ func NewKeeper(
4647
legacySubspace types.ParamSubspace,
4748
ics4Wrapper porttypes.ICS4Wrapper,
4849
channelKeeper types.ChannelKeeper,
50+
channelKeeperV2 types.ChannelKeeperV2,
4951
authKeeper types.AccountKeeper,
5052
bankKeeper types.BankKeeper,
5153
authority string,
@@ -60,14 +62,15 @@ func NewKeeper(
6062
}
6163

6264
return Keeper{
63-
cdc: cdc,
64-
storeService: storeService,
65-
legacySubspace: legacySubspace,
66-
ics4Wrapper: ics4Wrapper,
67-
channelKeeper: channelKeeper,
68-
AuthKeeper: authKeeper,
69-
BankKeeper: bankKeeper,
70-
authority: authority,
65+
cdc: cdc,
66+
storeService: storeService,
67+
legacySubspace: legacySubspace,
68+
ics4Wrapper: ics4Wrapper,
69+
channelKeeper: channelKeeper,
70+
channelKeeperV2: channelKeeperV2,
71+
AuthKeeper: authKeeper,
72+
BankKeeper: bankKeeper,
73+
authority: authority,
7174
}
7275
}
7376

modules/apps/transfer/keeper/keeper_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() {
6060
suite.chainA.GetSimApp().GetSubspace(types.ModuleName),
6161
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper,
6262
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper,
63+
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2,
6364
suite.chainA.GetSimApp().AccountKeeper,
6465
suite.chainA.GetSimApp().BankKeeper,
6566
suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(),
@@ -72,6 +73,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() {
7273
suite.chainA.GetSimApp().GetSubspace(types.ModuleName),
7374
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper,
7475
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper,
76+
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2,
7577
authkeeper.AccountKeeper{}, // empty account keeper
7678
suite.chainA.GetSimApp().BankKeeper,
7779
suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(),
@@ -84,6 +86,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() {
8486
suite.chainA.GetSimApp().GetSubspace(types.ModuleName),
8587
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper,
8688
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper,
89+
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2,
8790
suite.chainA.GetSimApp().AccountKeeper,
8891
suite.chainA.GetSimApp().BankKeeper,
8992
"", // authority

modules/apps/transfer/keeper/msg_server.go

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import (
1010
"github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/events"
1111
"github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/telemetry"
1212
"github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
13-
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
13+
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
14+
channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"
1415
ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors"
1516
)
1617

@@ -29,16 +30,6 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.
2930
return nil, err
3031
}
3132

32-
channel, found := k.channelKeeper.GetChannel(ctx, msg.SourcePort, msg.SourceChannel)
33-
if !found {
34-
return nil, errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", msg.SourcePort, msg.SourceChannel)
35-
}
36-
37-
appVersion, found := k.ics4Wrapper.GetAppVersion(ctx, msg.SourcePort, msg.SourceChannel)
38-
if !found {
39-
return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "application version not found for source port: %s and source channel: %s", msg.SourcePort, msg.SourceChannel)
40-
}
41-
4233
coin := msg.Token
4334

4435
// Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom.
@@ -54,31 +45,76 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.
5445
return nil, err
5546
}
5647

57-
if err := k.SendTransfer(ctx, msg.SourcePort, msg.SourceChannel, token, sender); err != nil {
58-
return nil, err
48+
packetData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, sender.String(), msg.Receiver, msg.Memo)
49+
50+
if err := packetData.ValidateBasic(); err != nil {
51+
return nil, errorsmod.Wrapf(err, "failed to validate %s packet data", types.V1)
5952
}
6053

61-
packetDataBytes, err := createPacketDataBytesFromVersion(
62-
appVersion, sender.String(), msg.Receiver, msg.Memo, token,
63-
)
54+
// if a channel exists with source channel, then use IBC V1 protocol
55+
// otherwise use IBC V2 protocol
56+
channel, isIBCV1 := k.channelKeeper.GetChannel(ctx, msg.SourcePort, msg.SourceChannel)
57+
58+
var sequence uint64
59+
if isIBCV1 {
60+
// if a V1 channel exists for the source channel, then use IBC V1 protocol
61+
sequence, err = k.transferV1Packet(ctx, msg.SourceChannel, token, msg.TimeoutHeight, msg.TimeoutTimestamp, packetData)
62+
// telemetry for transfer occurs here, in IBC V2 this is done in the onSendPacket callback
63+
telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, channel.Counterparty.PortId, channel.Counterparty.ChannelId, token)
64+
} else {
65+
// otherwise try to send an IBC V2 packet, if the sourceChannel is not a IBC V2 client
66+
// then core IBC will return a CounterpartyNotFound error
67+
sequence, err = k.transferV2Packet(ctx, msg.Encoding, msg.SourceChannel, msg.TimeoutTimestamp, packetData)
68+
}
6469
if err != nil {
6570
return nil, err
6671
}
6772

68-
sequence, err := k.ics4Wrapper.SendPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packetDataBytes)
73+
k.Logger(ctx).Info("IBC fungible token transfer", "token", coin, "sender", msg.Sender, "receiver", msg.Receiver)
74+
75+
return &types.MsgTransferResponse{Sequence: sequence}, nil
76+
}
77+
78+
func (k Keeper) transferV1Packet(ctx sdk.Context, sourceChannel string, token types.Token, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, packetData types.FungibleTokenPacketData) (uint64, error) {
79+
if err := k.SendTransfer(ctx, types.PortID, sourceChannel, token, sdk.MustAccAddressFromBech32(packetData.Sender)); err != nil {
80+
return 0, err
81+
}
82+
83+
packetDataBytes := packetData.GetBytes()
84+
sequence, err := k.ics4Wrapper.SendPacket(ctx, types.PortID, sourceChannel, timeoutHeight, timeoutTimestamp, packetDataBytes)
6985
if err != nil {
70-
return nil, err
86+
return 0, err
7187
}
7288

73-
events.EmitTransferEvent(ctx, sender.String(), msg.Receiver, token, msg.Memo)
89+
events.EmitTransferEvent(ctx, packetData.Sender, packetData.Receiver, token, packetData.Memo)
7490

75-
destinationPort := channel.Counterparty.PortId
76-
destinationChannel := channel.Counterparty.ChannelId
77-
telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, destinationPort, destinationChannel, token)
91+
return sequence, nil
92+
}
7893

79-
k.Logger(ctx).Info("IBC fungible token transfer", "token", coin, "sender", msg.Sender, "receiver", msg.Receiver)
94+
func (k Keeper) transferV2Packet(ctx sdk.Context, encoding, sourceChannel string, timeoutTimestamp uint64, packetData types.FungibleTokenPacketData) (uint64, error) {
95+
if encoding == "" {
96+
encoding = types.EncodingJSON
97+
}
8098

81-
return &types.MsgTransferResponse{Sequence: sequence}, nil
99+
data, err := types.MarshalPacketData(packetData, types.V1, encoding)
100+
if err != nil {
101+
return 0, err
102+
}
103+
104+
payload := channeltypesv2.NewPayload(
105+
types.PortID, types.PortID,
106+
types.V1, encoding, data,
107+
)
108+
msg := channeltypesv2.NewMsgSendPacket(
109+
sourceChannel, timeoutTimestamp,
110+
packetData.Sender, payload,
111+
)
112+
113+
res, err := k.channelKeeperV2.SendPacket(ctx, msg)
114+
if err != nil {
115+
return 0, err
116+
}
117+
return res.Sequence, nil
82118
}
83119

84120
// UpdateParams defines an rpc handler method for MsgUpdateParams. Updates the ibc-transfer module's parameters.

modules/apps/transfer/keeper/msg_server_test.go

Lines changed: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package keeper_test
33
import (
44
"errors"
55
"strings"
6+
"time"
67

78
sdk "github.com/cosmos/cosmos-sdk/types"
89
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
@@ -11,7 +12,8 @@ import (
1112
abci "github.com/cometbft/cometbft/abci/types"
1213

1314
"github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
14-
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
15+
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
16+
clienttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types"
1517
ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors"
1618
ibctesting "github.com/cosmos/ibc-go/v10/testing"
1719
)
@@ -90,7 +92,7 @@ func (suite *KeeperTestSuite) TestMsgTransfer() {
9092
func() {
9193
msg.SourceChannel = "channel-100"
9294
},
93-
channeltypes.ErrChannelNotFound,
95+
clienttypesv2.ErrCounterpartyNotFound,
9496
},
9597
}
9698

@@ -109,7 +111,7 @@ func (suite *KeeperTestSuite) TestMsgTransfer() {
109111
ibctesting.TestCoin,
110112
suite.chainA.SenderAccount.GetAddress().String(),
111113
suite.chainB.SenderAccount.GetAddress().String(),
112-
suite.chainB.GetTimeoutHeight(), 0, // only use timeout height
114+
clienttypes.Height{}, suite.chainB.GetTimeoutTimestamp(), // only use timeout height
113115
"memo",
114116
)
115117

@@ -160,6 +162,151 @@ func (suite *KeeperTestSuite) TestMsgTransfer() {
160162
}
161163
}
162164

165+
// TestMsgTransfer tests Transfer rpc handler with IBC V2 protocol
166+
func (suite *KeeperTestSuite) TestMsgTransferIBCV2() {
167+
var msg *types.MsgTransfer
168+
var path *ibctesting.Path
169+
170+
testCases := []struct {
171+
name string
172+
malleate func()
173+
expError error
174+
}{
175+
{
176+
"success",
177+
func() {
178+
msg.Token = ibctesting.TestCoin
179+
},
180+
nil,
181+
},
182+
{
183+
"bank send enabled for denoms",
184+
func() {
185+
err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(),
186+
banktypes.Params{
187+
SendEnabled: []*banktypes.SendEnabled{
188+
{Denom: sdk.DefaultBondDenom, Enabled: true},
189+
{Denom: ibctesting.SecondaryDenom, Enabled: true},
190+
},
191+
},
192+
)
193+
suite.Require().NoError(err)
194+
},
195+
nil,
196+
},
197+
{
198+
"failure: send transfers disabled",
199+
func() {
200+
suite.chainA.GetSimApp().TransferKeeper.SetParams(suite.chainA.GetContext(),
201+
types.Params{
202+
SendEnabled: false,
203+
},
204+
)
205+
},
206+
types.ErrSendDisabled,
207+
},
208+
{
209+
"failure: invalid sender",
210+
func() {
211+
msg.Sender = "address"
212+
},
213+
errors.New("decoding bech32 failed"),
214+
},
215+
{
216+
"failure: sender is a blocked address",
217+
func() {
218+
msg.Sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName).String()
219+
},
220+
ibcerrors.ErrUnauthorized,
221+
},
222+
{
223+
"failure: bank send disabled",
224+
func() {
225+
err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(),
226+
banktypes.Params{
227+
SendEnabled: []*banktypes.SendEnabled{{Denom: sdk.DefaultBondDenom, Enabled: false}},
228+
},
229+
)
230+
suite.Require().NoError(err)
231+
},
232+
types.ErrSendDisabled,
233+
},
234+
{
235+
"failure: client does not exist",
236+
func() {
237+
msg.SourceChannel = "07-tendermint-500"
238+
},
239+
clienttypesv2.ErrCounterpartyNotFound,
240+
},
241+
}
242+
243+
for _, tc := range testCases {
244+
tc := tc
245+
246+
suite.Run(tc.name, func() {
247+
suite.SetupTest()
248+
249+
path = ibctesting.NewPath(suite.chainA, suite.chainB)
250+
path.SetupV2()
251+
252+
timeoutTimestamp := uint64(suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix())
253+
254+
msg = types.NewMsgTransfer(
255+
types.PortID,
256+
path.EndpointA.ClientID, // use eureka client id
257+
ibctesting.TestCoin,
258+
suite.chainA.SenderAccount.GetAddress().String(),
259+
suite.chainB.SenderAccount.GetAddress().String(),
260+
clienttypes.Height{}, timeoutTimestamp, // only use timeout timestamp
261+
"memo",
262+
)
263+
264+
// send some coins of the second denom from bank module to the sender account as well
265+
err := suite.chainA.GetSimApp().BankKeeper.MintCoins(suite.chainA.GetContext(), types.ModuleName, sdk.NewCoins(ibctesting.SecondaryTestCoin))
266+
suite.Require().NoError(err)
267+
err = suite.chainA.GetSimApp().BankKeeper.SendCoinsFromModuleToAccount(suite.chainA.GetContext(), types.ModuleName, suite.chainA.SenderAccount.GetAddress(), sdk.NewCoins(ibctesting.SecondaryTestCoin))
268+
suite.Require().NoError(err)
269+
270+
tc.malleate()
271+
272+
ctx := suite.chainA.GetContext()
273+
274+
token, err := suite.chainA.GetSimApp().TransferKeeper.TokenFromCoin(ctx, msg.Token)
275+
suite.Require().NoError(err)
276+
277+
res, err := suite.chainA.GetSimApp().TransferKeeper.Transfer(ctx, msg)
278+
279+
// Verify events
280+
var expEvents []abci.Event
281+
events := ctx.EventManager().Events().ToABCIEvents()
282+
283+
expEvents = sdk.Events{
284+
sdk.NewEvent(types.EventTypeTransfer,
285+
sdk.NewAttribute(types.AttributeKeySender, msg.Sender),
286+
sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver),
287+
sdk.NewAttribute(types.AttributeKeyDenom, token.Denom.Path()),
288+
sdk.NewAttribute(types.AttributeKeyAmount, msg.Token.Amount.String()),
289+
sdk.NewAttribute(types.AttributeKeyMemo, msg.Memo),
290+
),
291+
sdk.NewEvent(
292+
sdk.EventTypeMessage,
293+
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
294+
),
295+
}.ToABCIEvents()
296+
297+
if tc.expError == nil {
298+
suite.Require().NoError(err)
299+
suite.Require().NotNil(res)
300+
suite.Require().NotEqual(res.Sequence, uint64(0))
301+
ibctesting.AssertEvents(&suite.Suite, expEvents, events)
302+
} else {
303+
suite.Require().Nil(res)
304+
suite.Require().True(errors.Is(err, tc.expError) || strings.Contains(err.Error(), tc.expError.Error()), err.Error())
305+
}
306+
})
307+
}
308+
}
309+
163310
// TestUpdateParams tests UpdateParams rpc handler
164311
func (suite *KeeperTestSuite) TestUpdateParams() {
165312
signer := suite.chainA.GetSimApp().TransferKeeper.GetAuthority()

modules/apps/transfer/keeper/relay_test.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,6 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
147147
},
148148
types.ErrInvalidAmount,
149149
},
150-
{
151-
"failure: source channel not found",
152-
func() {
153-
// channel references wrong ID
154-
path.EndpointA.ChannelID = ibctesting.InvalidID
155-
},
156-
channeltypes.ErrChannelNotFound,
157-
},
158150
{
159151
"failure: sender account is blocked",
160152
func() {

0 commit comments

Comments
 (0)