diff --git a/modules/apps/callbacks/testing/simapp/app.go b/modules/apps/callbacks/testing/simapp/app.go index 103ace1277f..764d430f802 100644 --- a/modules/apps/callbacks/testing/simapp/app.go +++ b/modules/apps/callbacks/testing/simapp/app.go @@ -390,6 +390,7 @@ func NewSimApp( appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeperV2, app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/modules/apps/transfer/keeper/keeper.go b/modules/apps/transfer/keeper/keeper.go index 799c8eb218e..ba3ed9dff7c 100644 --- a/modules/apps/transfer/keeper/keeper.go +++ b/modules/apps/transfer/keeper/keeper.go @@ -29,10 +29,11 @@ type Keeper struct { cdc codec.BinaryCodec legacySubspace types.ParamSubspace - ics4Wrapper porttypes.ICS4Wrapper - channelKeeper types.ChannelKeeper - AuthKeeper types.AccountKeeper - BankKeeper types.BankKeeper + ics4Wrapper porttypes.ICS4Wrapper + channelKeeper types.ChannelKeeper + channelKeeperV2 types.ChannelKeeperV2 + AuthKeeper types.AccountKeeper + BankKeeper types.BankKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. @@ -46,6 +47,7 @@ func NewKeeper( legacySubspace types.ParamSubspace, ics4Wrapper porttypes.ICS4Wrapper, channelKeeper types.ChannelKeeper, + channelKeeperV2 types.ChannelKeeperV2, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, authority string, @@ -60,14 +62,15 @@ func NewKeeper( } return Keeper{ - cdc: cdc, - storeService: storeService, - legacySubspace: legacySubspace, - ics4Wrapper: ics4Wrapper, - channelKeeper: channelKeeper, - AuthKeeper: authKeeper, - BankKeeper: bankKeeper, - authority: authority, + cdc: cdc, + storeService: storeService, + legacySubspace: legacySubspace, + ics4Wrapper: ics4Wrapper, + channelKeeper: channelKeeper, + channelKeeperV2: channelKeeperV2, + AuthKeeper: authKeeper, + BankKeeper: bankKeeper, + authority: authority, } } diff --git a/modules/apps/transfer/keeper/keeper_test.go b/modules/apps/transfer/keeper/keeper_test.go index e80c8400e68..a3f9c77e75c 100644 --- a/modules/apps/transfer/keeper/keeper_test.go +++ b/modules/apps/transfer/keeper/keeper_test.go @@ -60,6 +60,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { suite.chainA.GetSimApp().GetSubspace(types.ModuleName), suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2, suite.chainA.GetSimApp().AccountKeeper, suite.chainA.GetSimApp().BankKeeper, suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(), @@ -72,6 +73,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { suite.chainA.GetSimApp().GetSubspace(types.ModuleName), suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2, authkeeper.AccountKeeper{}, // empty account keeper suite.chainA.GetSimApp().BankKeeper, suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(), @@ -84,6 +86,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { suite.chainA.GetSimApp().GetSubspace(types.ModuleName), suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2, suite.chainA.GetSimApp().AccountKeeper, suite.chainA.GetSimApp().BankKeeper, "", // authority diff --git a/modules/apps/transfer/keeper/msg_server.go b/modules/apps/transfer/keeper/msg_server.go index 74d5d59f2e5..5b0956cb2a1 100644 --- a/modules/apps/transfer/keeper/msg_server.go +++ b/modules/apps/transfer/keeper/msg_server.go @@ -10,7 +10,8 @@ import ( "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/events" "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/telemetry" "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" ) @@ -29,16 +30,6 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types. return nil, err } - channel, found := k.channelKeeper.GetChannel(ctx, msg.SourcePort, msg.SourceChannel) - if !found { - return nil, errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", msg.SourcePort, msg.SourceChannel) - } - - appVersion, found := k.ics4Wrapper.GetAppVersion(ctx, msg.SourcePort, msg.SourceChannel) - if !found { - return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "application version not found for source port: %s and source channel: %s", msg.SourcePort, msg.SourceChannel) - } - coin := msg.Token // 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. return nil, err } - if err := k.SendTransfer(ctx, msg.SourcePort, msg.SourceChannel, token, sender); err != nil { - return nil, err + packetData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, sender.String(), msg.Receiver, msg.Memo) + + if err := packetData.ValidateBasic(); err != nil { + return nil, errorsmod.Wrapf(err, "failed to validate %s packet data", types.V1) } - packetDataBytes, err := createPacketDataBytesFromVersion( - appVersion, sender.String(), msg.Receiver, msg.Memo, token, - ) + // if a channel exists with source channel, then use IBC V1 protocol + // otherwise use IBC V2 protocol + channel, isIBCV1 := k.channelKeeper.GetChannel(ctx, msg.SourcePort, msg.SourceChannel) + + var sequence uint64 + if isIBCV1 { + // if a V1 channel exists for the source channel, then use IBC V1 protocol + sequence, err = k.transferV1Packet(ctx, msg.SourceChannel, token, msg.TimeoutHeight, msg.TimeoutTimestamp, packetData) + // telemetry for transfer occurs here, in IBC V2 this is done in the onSendPacket callback + telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, channel.Counterparty.PortId, channel.Counterparty.ChannelId, token) + } else { + // otherwise try to send an IBC V2 packet, if the sourceChannel is not a IBC V2 client + // then core IBC will return a CounterpartyNotFound error + sequence, err = k.transferV2Packet(ctx, msg.Encoding, msg.SourceChannel, msg.TimeoutTimestamp, packetData) + } if err != nil { return nil, err } - sequence, err := k.ics4Wrapper.SendPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packetDataBytes) + k.Logger(ctx).Info("IBC fungible token transfer", "token", coin, "sender", msg.Sender, "receiver", msg.Receiver) + + return &types.MsgTransferResponse{Sequence: sequence}, nil +} + +func (k Keeper) transferV1Packet(ctx sdk.Context, sourceChannel string, token types.Token, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, packetData types.FungibleTokenPacketData) (uint64, error) { + if err := k.SendTransfer(ctx, types.PortID, sourceChannel, token, sdk.MustAccAddressFromBech32(packetData.Sender)); err != nil { + return 0, err + } + + packetDataBytes := packetData.GetBytes() + sequence, err := k.ics4Wrapper.SendPacket(ctx, types.PortID, sourceChannel, timeoutHeight, timeoutTimestamp, packetDataBytes) if err != nil { - return nil, err + return 0, err } - events.EmitTransferEvent(ctx, sender.String(), msg.Receiver, token, msg.Memo) + events.EmitTransferEvent(ctx, packetData.Sender, packetData.Receiver, token, packetData.Memo) - destinationPort := channel.Counterparty.PortId - destinationChannel := channel.Counterparty.ChannelId - telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, destinationPort, destinationChannel, token) + return sequence, nil +} - k.Logger(ctx).Info("IBC fungible token transfer", "token", coin, "sender", msg.Sender, "receiver", msg.Receiver) +func (k Keeper) transferV2Packet(ctx sdk.Context, encoding, sourceChannel string, timeoutTimestamp uint64, packetData types.FungibleTokenPacketData) (uint64, error) { + if encoding == "" { + encoding = types.EncodingJSON + } - return &types.MsgTransferResponse{Sequence: sequence}, nil + data, err := types.MarshalPacketData(packetData, types.V1, encoding) + if err != nil { + return 0, err + } + + payload := channeltypesv2.NewPayload( + types.PortID, types.PortID, + types.V1, encoding, data, + ) + msg := channeltypesv2.NewMsgSendPacket( + sourceChannel, timeoutTimestamp, + packetData.Sender, payload, + ) + + res, err := k.channelKeeperV2.SendPacket(ctx, msg) + if err != nil { + return 0, err + } + return res.Sequence, nil } // UpdateParams defines an rpc handler method for MsgUpdateParams. Updates the ibc-transfer module's parameters. diff --git a/modules/apps/transfer/keeper/msg_server_test.go b/modules/apps/transfer/keeper/msg_server_test.go index e658bdf648e..312f8557925 100644 --- a/modules/apps/transfer/keeper/msg_server_test.go +++ b/modules/apps/transfer/keeper/msg_server_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "errors" "strings" + "time" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -11,7 +12,8 @@ import ( abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clienttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" ibctesting "github.com/cosmos/ibc-go/v10/testing" ) @@ -90,7 +92,7 @@ func (suite *KeeperTestSuite) TestMsgTransfer() { func() { msg.SourceChannel = "channel-100" }, - channeltypes.ErrChannelNotFound, + clienttypesv2.ErrCounterpartyNotFound, }, } @@ -109,7 +111,7 @@ func (suite *KeeperTestSuite) TestMsgTransfer() { ibctesting.TestCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), - suite.chainB.GetTimeoutHeight(), 0, // only use timeout height + clienttypes.Height{}, suite.chainB.GetTimeoutTimestamp(), // only use timeout height "memo", ) @@ -160,6 +162,151 @@ func (suite *KeeperTestSuite) TestMsgTransfer() { } } +// TestMsgTransfer tests Transfer rpc handler with IBC V2 protocol +func (suite *KeeperTestSuite) TestMsgTransferIBCV2() { + var msg *types.MsgTransfer + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + msg.Token = ibctesting.TestCoin + }, + nil, + }, + { + "bank send enabled for denoms", + func() { + err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{ + {Denom: sdk.DefaultBondDenom, Enabled: true}, + {Denom: ibctesting.SecondaryDenom, Enabled: true}, + }, + }, + ) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: send transfers disabled", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetParams(suite.chainA.GetContext(), + types.Params{ + SendEnabled: false, + }, + ) + }, + types.ErrSendDisabled, + }, + { + "failure: invalid sender", + func() { + msg.Sender = "address" + }, + errors.New("decoding bech32 failed"), + }, + { + "failure: sender is a blocked address", + func() { + msg.Sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName).String() + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: bank send disabled", + func() { + err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{{Denom: sdk.DefaultBondDenom, Enabled: false}}, + }, + ) + suite.Require().NoError(err) + }, + types.ErrSendDisabled, + }, + { + "failure: client does not exist", + func() { + msg.SourceChannel = "07-tendermint-500" + }, + clienttypesv2.ErrCounterpartyNotFound, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + timeoutTimestamp := uint64(suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix()) + + msg = types.NewMsgTransfer( + types.PortID, + path.EndpointA.ClientID, // use eureka client id + ibctesting.TestCoin, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), + clienttypes.Height{}, timeoutTimestamp, // only use timeout timestamp + "memo", + ) + + // send some coins of the second denom from bank module to the sender account as well + err := suite.chainA.GetSimApp().BankKeeper.MintCoins(suite.chainA.GetContext(), types.ModuleName, sdk.NewCoins(ibctesting.SecondaryTestCoin)) + suite.Require().NoError(err) + err = suite.chainA.GetSimApp().BankKeeper.SendCoinsFromModuleToAccount(suite.chainA.GetContext(), types.ModuleName, suite.chainA.SenderAccount.GetAddress(), sdk.NewCoins(ibctesting.SecondaryTestCoin)) + suite.Require().NoError(err) + + tc.malleate() + + ctx := suite.chainA.GetContext() + + token, err := suite.chainA.GetSimApp().TransferKeeper.TokenFromCoin(ctx, msg.Token) + suite.Require().NoError(err) + + res, err := suite.chainA.GetSimApp().TransferKeeper.Transfer(ctx, msg) + + // Verify events + var expEvents []abci.Event + events := ctx.EventManager().Events().ToABCIEvents() + + expEvents = sdk.Events{ + sdk.NewEvent(types.EventTypeTransfer, + sdk.NewAttribute(types.AttributeKeySender, msg.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, token.Denom.Path()), + sdk.NewAttribute(types.AttributeKeyAmount, msg.Token.Amount.String()), + sdk.NewAttribute(types.AttributeKeyMemo, msg.Memo), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }.ToABCIEvents() + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().NotEqual(res.Sequence, uint64(0)) + ibctesting.AssertEvents(&suite.Suite, expEvents, events) + } else { + suite.Require().Nil(res) + suite.Require().True(errors.Is(err, tc.expError) || strings.Contains(err.Error(), tc.expError.Error()), err.Error()) + } + }) + } +} + // TestUpdateParams tests UpdateParams rpc handler func (suite *KeeperTestSuite) TestUpdateParams() { signer := suite.chainA.GetSimApp().TransferKeeper.GetAuthority() diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index 2d4531b55d6..9cad6e1585d 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -147,14 +147,6 @@ func (suite *KeeperTestSuite) TestSendTransfer() { }, types.ErrInvalidAmount, }, - { - "failure: source channel not found", - func() { - // channel references wrong ID - path.EndpointA.ChannelID = ibctesting.InvalidID - }, - channeltypes.ErrChannelNotFound, - }, { "failure: sender account is blocked", func() { diff --git a/modules/apps/transfer/types/expected_keepers.go b/modules/apps/transfer/types/expected_keepers.go index ce17aca840b..73077c6fc05 100644 --- a/modules/apps/transfer/types/expected_keepers.go +++ b/modules/apps/transfer/types/expected_keepers.go @@ -7,9 +7,10 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + clienttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" - ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" ) // AccountKeeper defines the contract required for account APIs. @@ -41,9 +42,14 @@ type ChannelKeeper interface { HasChannel(ctx sdk.Context, portID, channelID string) bool } +// ChannelKeeperV2 defines the expected IBC channelV2 keeper +type ChannelKeeperV2 interface { + SendPacket(ctx context.Context, msg *channeltypesv2.MsgSendPacket) (*channeltypesv2.MsgSendPacketResponse, error) +} + // ClientKeeper defines the expected IBC client keeper -type ClientKeeper interface { - GetClientConsensusState(ctx sdk.Context, clientID string) (connection ibcexported.ConsensusState, found bool) +type ClientKeeperV2 interface { + GetClientCounterparty(ctx sdk.Context, clientID string) (counterparty clienttypesv2.CounterpartyInfo, found bool) } // ConnectionKeeper defines the expected IBC connection keeper diff --git a/modules/apps/transfer/types/msgs.go b/modules/apps/transfer/types/msgs.go index fe4f61913b3..bd9bf3c2dc5 100644 --- a/modules/apps/transfer/types/msgs.go +++ b/modules/apps/transfer/types/msgs.go @@ -61,6 +61,27 @@ func NewMsgTransfer( } } +// NewMsgTransferWithEncoding creates a new MsgTransfer instance +// with the provided encoding +func NewMsgTransferWithEncoding( + sourcePort, sourceChannel string, + token sdk.Coin, sender, receiver string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, + memo string, encoding string, +) *MsgTransfer { + return &MsgTransfer{ + SourcePort: sourcePort, + SourceChannel: sourceChannel, + Token: token, + Sender: sender, + Receiver: receiver, + TimeoutHeight: timeoutHeight, + TimeoutTimestamp: timeoutTimestamp, + Memo: memo, + Encoding: encoding, + } +} + // ValidateBasic performs a basic check of the MsgTransfer fields. // NOTE: timeout height or timestamp values can be 0 to disable the timeout. // NOTE: The recipient addresses format is not validated as the format defined by diff --git a/modules/apps/transfer/types/msgs_test.go b/modules/apps/transfer/types/msgs_test.go index a821d8f8af3..ef35aab1992 100644 --- a/modules/apps/transfer/types/msgs_test.go +++ b/modules/apps/transfer/types/msgs_test.go @@ -28,6 +28,7 @@ const ( invalidLongPort = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eros neque, ultricies vel ligula ac, convallis porttitor elit. Maecenas tincidunt turpis elit, vel faucibus nisl pellentesque sodales" validChannel = "testchannel" + eurekaClient = "07-tendermint-0" invalidChannel = "(invalidchannel1)" invalidShortChannel = "invalid" invalidLongChannel = "invalidlongchannelinvalidlongchannelinvalidlongchannelinvalidlongchannel" @@ -57,7 +58,11 @@ func TestMsgTransferValidation(t *testing.T) { expError error }{ {"valid msg with base denom", types.NewMsgTransfer(validPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with base denom", types.NewMsgTransfer(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with base denom and encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json"), nil}, {"valid msg with trace hash", types.NewMsgTransfer(validPort, validChannel, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with trace hash", types.NewMsgTransfer(validPort, eurekaClient, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with trace hash with encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json"), nil}, {"invalid ibc denom", types.NewMsgTransfer(validPort, validChannel, invalidIBCCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidCoins}, {"too short port id", types.NewMsgTransfer(invalidShortPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, {"too long port id", types.NewMsgTransfer(invalidLongPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, diff --git a/modules/apps/transfer/types/packet.go b/modules/apps/transfer/types/packet.go index fa195912622..49eb79ac565 100644 --- a/modules/apps/transfer/types/packet.go +++ b/modules/apps/transfer/types/packet.go @@ -189,6 +189,24 @@ func (ftpd FungibleTokenPacketDataV2) GetPacketSender(sourcePortID string) strin return ftpd.Sender } +// MarshalPacketData attempts to marshal the provided FungibleTokenPacketData into bytes with the provided encoding. +func MarshalPacketData(data FungibleTokenPacketData, ics20Version string, encoding string) ([]byte, error) { + if ics20Version != V1 { + panic("unsupported ics20 version") + } + + switch encoding { + case EncodingJSON: + return json.Marshal(data) + case EncodingProtobuf: + return proto.Marshal(&data) + case EncodingABI: + return EncodeABIFungibleTokenPacketData(&data) + default: + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "invalid encoding provided, must be either empty or one of [%q, %q], got %s", EncodingJSON, EncodingProtobuf, encoding) + } +} + // UnmarshalPacketData attempts to unmarshal the provided packet data bytes into a FungibleTokenPacketDataV2. func UnmarshalPacketData(bz []byte, ics20Version string, encoding string) (FungibleTokenPacketDataV2, error) { const failedUnmarshalingErrorMsg = "cannot unmarshal %s transfer packet data: %s" diff --git a/modules/apps/transfer/types/packet_test.go b/modules/apps/transfer/types/packet_test.go index 315530bc3ed..603199a90ff 100644 --- a/modules/apps/transfer/types/packet_test.go +++ b/modules/apps/transfer/types/packet_test.go @@ -5,7 +5,6 @@ import ( "fmt" "testing" - "github.com/cosmos/gogoproto/proto" "github.com/stretchr/testify/require" errorsmod "cosmossdk.io/errors" @@ -543,7 +542,11 @@ func TestUnmarshalPacketData(t *testing.T) { { "success: v1 -> v2 with JSON encoding", func() { + packetDataV1 := types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, "") encoding = types.EncodingJSON + bz, err := types.MarshalPacketData(packetDataV1, types.V1, encoding) + require.NoError(t, err) + packetDataBz = bz }, nil, }, @@ -551,7 +554,7 @@ func TestUnmarshalPacketData(t *testing.T) { "success: v1 -> v2 with protobuf encoding", func() { packetData := types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, "") - bz, err := proto.Marshal(&packetData) + bz, err := types.MarshalPacketData(packetData, types.V1, types.EncodingProtobuf) require.NoError(t, err) packetDataBz = bz @@ -563,7 +566,7 @@ func TestUnmarshalPacketData(t *testing.T) { "success: v1 -> v2 with abi encoding", func() { packetData := types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, "") - bz, err := types.EncodeABIFungibleTokenPacketData(&packetData) + bz, err := types.MarshalPacketData(packetData, types.V1, types.EncodingABI) require.NoError(t, err) packetDataBz = bz diff --git a/modules/apps/transfer/types/tx.pb.go b/modules/apps/transfer/types/tx.pb.go index fd7992cedff..7d5da5e7b14 100644 --- a/modules/apps/transfer/types/tx.pb.go +++ b/modules/apps/transfer/types/tx.pb.go @@ -54,6 +54,8 @@ type MsgTransfer struct { TimeoutTimestamp uint64 `protobuf:"varint,7,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` // optional memo Memo string `protobuf:"bytes,8,opt,name=memo,proto3" json:"memo,omitempty"` + // optional encoding + Encoding string `protobuf:"bytes,9,opt,name=encoding,proto3" json:"encoding,omitempty"` } func (m *MsgTransfer) Reset() { *m = MsgTransfer{} } @@ -221,46 +223,47 @@ func init() { } var fileDescriptor_7401ed9bed2f8e09 = []byte{ - // 612 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x3f, 0x6f, 0xd3, 0x40, - 0x14, 0x8f, 0x69, 0x1a, 0xda, 0x0b, 0x6d, 0xa9, 0x41, 0xad, 0x6b, 0x21, 0xa7, 0x8a, 0xa8, 0x54, - 0x52, 0xf5, 0x0e, 0x17, 0x21, 0x50, 0xc6, 0x74, 0x61, 0xa0, 0x52, 0x65, 0x95, 0x85, 0xa5, 0xb2, - 0x2f, 0x0f, 0xe7, 0xd4, 0xf8, 0xce, 0xf8, 0x2e, 0x11, 0x2c, 0xa8, 0x62, 0x42, 0x4c, 0x7c, 0x04, - 0x46, 0xc6, 0x7e, 0x8c, 0x8e, 0x1d, 0x99, 0x10, 0x6a, 0x87, 0x2e, 0x7c, 0x08, 0x74, 0xe7, 0x4b, - 0x30, 0x0c, 0x01, 0x16, 0xfb, 0xfd, 0xf9, 0xbd, 0x7f, 0xbf, 0x77, 0x0f, 0x6d, 0xb1, 0x84, 0x92, - 0x38, 0xcf, 0x87, 0x8c, 0xc6, 0x8a, 0x09, 0x2e, 0x89, 0x2a, 0x62, 0x2e, 0x5f, 0x41, 0x41, 0xc6, - 0x21, 0x51, 0x6f, 0x70, 0x5e, 0x08, 0x25, 0xdc, 0x7b, 0x2c, 0xa1, 0xb8, 0x0a, 0xc3, 0x13, 0x18, - 0x1e, 0x87, 0xfe, 0x6a, 0x9c, 0x31, 0x2e, 0x88, 0xf9, 0x96, 0x01, 0xfe, 0xdd, 0x54, 0xa4, 0xc2, - 0x88, 0x44, 0x4b, 0xd6, 0xba, 0x4e, 0x85, 0xcc, 0x84, 0x24, 0x99, 0x4c, 0x75, 0xfa, 0x4c, 0xa6, - 0xd6, 0x11, 0x58, 0x47, 0x12, 0x4b, 0x20, 0xe3, 0x30, 0x01, 0x15, 0x87, 0x84, 0x0a, 0xc6, 0xad, - 0xbf, 0xa5, 0xdb, 0xa4, 0xa2, 0x00, 0x42, 0x87, 0x0c, 0xb8, 0xd2, 0xd1, 0xa5, 0x64, 0x01, 0x3b, - 0xb3, 0xe7, 0x98, 0x34, 0x6b, 0xc0, 0xed, 0xd3, 0x39, 0xd4, 0x3c, 0x90, 0xe9, 0x91, 0xb5, 0xba, - 0x2d, 0xd4, 0x94, 0x62, 0x54, 0x50, 0x38, 0xce, 0x45, 0xa1, 0x3c, 0x67, 0xd3, 0xd9, 0x5e, 0x8c, - 0x50, 0x69, 0x3a, 0x14, 0x85, 0x72, 0xb7, 0xd0, 0xb2, 0x05, 0xd0, 0x41, 0xcc, 0x39, 0x0c, 0xbd, - 0x1b, 0x06, 0xb3, 0x54, 0x5a, 0xf7, 0x4b, 0xa3, 0xdb, 0x45, 0xf3, 0x4a, 0x9c, 0x00, 0xf7, 0xe6, - 0x36, 0x9d, 0xed, 0xe6, 0xde, 0x06, 0x2e, 0xa7, 0xc2, 0x7a, 0x2a, 0x6c, 0xa7, 0xc2, 0xfb, 0x82, - 0xf1, 0xde, 0xe2, 0xf9, 0xb7, 0x56, 0xed, 0xcb, 0xf5, 0x59, 0xc7, 0x89, 0xca, 0x10, 0x77, 0x0d, - 0x35, 0x24, 0xf0, 0x3e, 0x14, 0x5e, 0xdd, 0xa4, 0xb6, 0x9a, 0xeb, 0xa3, 0x85, 0x02, 0x28, 0xb0, - 0x31, 0x14, 0xde, 0xbc, 0xf1, 0x4c, 0x75, 0xf7, 0x39, 0x5a, 0x56, 0x2c, 0x03, 0x31, 0x52, 0xc7, - 0x03, 0x60, 0xe9, 0x40, 0x79, 0x0d, 0x53, 0xd8, 0xc7, 0x7a, 0x5d, 0x9a, 0x2e, 0x6c, 0x49, 0x1a, - 0x87, 0xf8, 0x99, 0x41, 0x54, 0x2b, 0x2f, 0xd9, 0xe0, 0xd2, 0xe3, 0xee, 0xa0, 0xd5, 0x49, 0x36, - 0xfd, 0x97, 0x2a, 0xce, 0x72, 0xef, 0xe6, 0xa6, 0xb3, 0x5d, 0x8f, 0x6e, 0x5b, 0xc7, 0xd1, 0xc4, - 0xee, 0xba, 0xa8, 0x9e, 0x41, 0x26, 0xbc, 0x05, 0xd3, 0x92, 0x91, 0xbb, 0x9d, 0x0f, 0x9f, 0x5b, - 0xb5, 0xf7, 0xd7, 0x67, 0x1d, 0xdb, 0xfb, 0xc7, 0xeb, 0xb3, 0xce, 0x5a, 0x49, 0xc1, 0xae, 0xec, - 0x9f, 0x90, 0x0a, 0xe5, 0xed, 0x27, 0xe8, 0x4e, 0x45, 0x8d, 0x40, 0xe6, 0x82, 0x4b, 0xd0, 0xd3, - 0x4a, 0x78, 0x3d, 0x02, 0x4e, 0xc1, 0xac, 0xa1, 0x1e, 0x4d, 0xf5, 0x6e, 0x5d, 0xa7, 0x6f, 0xbf, - 0x43, 0x2b, 0x07, 0x32, 0x7d, 0x91, 0xf7, 0x63, 0x05, 0x87, 0x71, 0x11, 0x67, 0xd2, 0x50, 0xc7, - 0x52, 0x0e, 0x85, 0xdd, 0x9c, 0xd5, 0xdc, 0x1e, 0x6a, 0xe4, 0x06, 0x61, 0xb6, 0xd5, 0xdc, 0xbb, - 0x8f, 0x67, 0xbd, 0x62, 0x5c, 0x66, 0xeb, 0xd5, 0x35, 0x41, 0x91, 0x8d, 0xec, 0xae, 0xfc, 0x9a, - 0xc9, 0x24, 0x6d, 0x6f, 0xa0, 0xf5, 0x3f, 0xea, 0x4f, 0x9a, 0xdf, 0xfb, 0xe1, 0xa0, 0xb9, 0x03, - 0x99, 0xba, 0x03, 0xb4, 0x30, 0x7d, 0x5a, 0x0f, 0x66, 0xd7, 0xac, 0x70, 0xe0, 0x87, 0xff, 0x0c, - 0x9d, 0xd2, 0xa5, 0xd0, 0xad, 0xdf, 0x98, 0xd8, 0xfd, 0x6b, 0x8a, 0x2a, 0xdc, 0x7f, 0xfc, 0x5f, - 0xf0, 0x49, 0x55, 0x7f, 0xfe, 0x54, 0x3f, 0x9f, 0x5e, 0x74, 0x7e, 0x19, 0x38, 0x17, 0x97, 0x81, - 0xf3, 0xfd, 0x32, 0x70, 0x3e, 0x5d, 0x05, 0xb5, 0x8b, 0xab, 0xa0, 0xf6, 0xf5, 0x2a, 0xa8, 0xbd, - 0x7c, 0x9a, 0x32, 0x35, 0x18, 0x25, 0x98, 0x8a, 0x8c, 0xd8, 0xc3, 0x66, 0x09, 0xdd, 0x4d, 0x05, - 0x19, 0x87, 0x0f, 0x49, 0x26, 0xfa, 0xa3, 0x21, 0x48, 0x7d, 0xad, 0x95, 0x2b, 0x55, 0x6f, 0x73, - 0x90, 0x49, 0xc3, 0x1c, 0xe8, 0xa3, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x35, 0xce, 0x82, 0x0b, - 0x97, 0x04, 0x00, 0x00, + // 629 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x31, 0x6f, 0xd4, 0x4a, + 0x10, 0x3e, 0xbf, 0x5c, 0xee, 0x25, 0x7b, 0x2f, 0xc9, 0x8b, 0x41, 0x89, 0x63, 0x21, 0x5f, 0x74, + 0x22, 0x52, 0xb8, 0x28, 0xbb, 0x38, 0x08, 0x81, 0xae, 0xbc, 0x34, 0x14, 0x44, 0x8a, 0xac, 0xd0, + 0xd0, 0x44, 0xf6, 0xde, 0xe0, 0x5b, 0xe5, 0xbc, 0x6b, 0xbc, 0x7b, 0x27, 0x68, 0x10, 0xa2, 0x42, + 0x54, 0xfc, 0x00, 0x0a, 0x4a, 0xca, 0xfc, 0x8c, 0x94, 0x29, 0xa9, 0x10, 0x4a, 0x8a, 0x34, 0xfc, + 0x08, 0xb4, 0xeb, 0xf5, 0x61, 0x28, 0x02, 0x34, 0xf6, 0xce, 0xcc, 0x37, 0xdf, 0xcc, 0x7c, 0xb3, + 0x8b, 0xb6, 0x58, 0x42, 0x49, 0x9c, 0xe7, 0x63, 0x46, 0x63, 0xc5, 0x04, 0x97, 0x44, 0x15, 0x31, + 0x97, 0xcf, 0xa0, 0x20, 0xd3, 0x90, 0xa8, 0x17, 0x38, 0x2f, 0x84, 0x12, 0xee, 0x2d, 0x96, 0x50, + 0x5c, 0x87, 0xe1, 0x0a, 0x86, 0xa7, 0xa1, 0xbf, 0x1a, 0x67, 0x8c, 0x0b, 0x62, 0xbe, 0x65, 0x82, + 0x7f, 0x33, 0x15, 0xa9, 0x30, 0x47, 0xa2, 0x4f, 0xd6, 0xbb, 0x4e, 0x85, 0xcc, 0x84, 0x24, 0x99, + 0x4c, 0x35, 0x7d, 0x26, 0x53, 0x1b, 0x08, 0x6c, 0x20, 0x89, 0x25, 0x90, 0x69, 0x98, 0x80, 0x8a, + 0x43, 0x42, 0x05, 0xe3, 0x36, 0xde, 0xd1, 0x6d, 0x52, 0x51, 0x00, 0xa1, 0x63, 0x06, 0x5c, 0xe9, + 0xec, 0xf2, 0x64, 0x01, 0x3b, 0xd7, 0xcf, 0x51, 0x35, 0x6b, 0xc0, 0xdd, 0x0f, 0x73, 0xa8, 0x7d, + 0x20, 0xd3, 0x23, 0xeb, 0x75, 0x3b, 0xa8, 0x2d, 0xc5, 0xa4, 0xa0, 0x70, 0x9c, 0x8b, 0x42, 0x79, + 0xce, 0xa6, 0xb3, 0xbd, 0x18, 0xa1, 0xd2, 0x75, 0x28, 0x0a, 0xe5, 0x6e, 0xa1, 0x65, 0x0b, 0xa0, + 0xa3, 0x98, 0x73, 0x18, 0x7b, 0xff, 0x18, 0xcc, 0x52, 0xe9, 0xdd, 0x2f, 0x9d, 0x6e, 0x1f, 0xcd, + 0x2b, 0x71, 0x02, 0xdc, 0x9b, 0xdb, 0x74, 0xb6, 0xdb, 0x7b, 0x1b, 0xb8, 0x9c, 0x0a, 0xeb, 0xa9, + 0xb0, 0x9d, 0x0a, 0xef, 0x0b, 0xc6, 0x07, 0x8b, 0x67, 0x5f, 0x3a, 0x8d, 0x4f, 0x57, 0xa7, 0x3d, + 0x27, 0x2a, 0x53, 0xdc, 0x35, 0xd4, 0x92, 0xc0, 0x87, 0x50, 0x78, 0x4d, 0x43, 0x6d, 0x2d, 0xd7, + 0x47, 0x0b, 0x05, 0x50, 0x60, 0x53, 0x28, 0xbc, 0x79, 0x13, 0x99, 0xd9, 0xee, 0x63, 0xb4, 0xac, + 0x58, 0x06, 0x62, 0xa2, 0x8e, 0x47, 0xc0, 0xd2, 0x91, 0xf2, 0x5a, 0xa6, 0xb0, 0x8f, 0xf5, 0xba, + 0xb4, 0x5c, 0xd8, 0x8a, 0x34, 0x0d, 0xf1, 0x23, 0x83, 0xa8, 0x57, 0x5e, 0xb2, 0xc9, 0x65, 0xc4, + 0xdd, 0x41, 0xab, 0x15, 0x9b, 0xfe, 0x4b, 0x15, 0x67, 0xb9, 0xf7, 0xef, 0xa6, 0xb3, 0xdd, 0x8c, + 0xfe, 0xb7, 0x81, 0xa3, 0xca, 0xef, 0xba, 0xa8, 0x99, 0x41, 0x26, 0xbc, 0x05, 0xd3, 0x92, 0x39, + 0xeb, 0x56, 0x81, 0x53, 0x31, 0x64, 0x3c, 0xf5, 0x16, 0xcb, 0x56, 0x2b, 0xbb, 0xdf, 0x7b, 0xfb, + 0xb1, 0xd3, 0x78, 0x73, 0x75, 0xda, 0xb3, 0x73, 0xbd, 0xbb, 0x3a, 0xed, 0xad, 0x95, 0xf2, 0xec, + 0xca, 0xe1, 0x09, 0xa9, 0xad, 0xa3, 0xfb, 0x00, 0xdd, 0xa8, 0x99, 0x11, 0xc8, 0x5c, 0x70, 0x09, + 0x9a, 0x5e, 0xc2, 0xf3, 0x09, 0x70, 0x0a, 0x66, 0x45, 0xcd, 0x68, 0x66, 0xf7, 0x9b, 0x9a, 0xbe, + 0xfb, 0x0a, 0xad, 0x1c, 0xc8, 0xf4, 0x49, 0x3e, 0x8c, 0x15, 0x1c, 0xc6, 0x45, 0x9c, 0x49, 0x23, + 0x2b, 0x4b, 0x39, 0x14, 0x76, 0xab, 0xd6, 0x72, 0x07, 0xa8, 0x95, 0x1b, 0x84, 0xd9, 0x64, 0x7b, + 0xef, 0x36, 0xbe, 0xee, 0x86, 0xe3, 0x92, 0x6d, 0xd0, 0xd4, 0xe2, 0x45, 0x36, 0xb3, 0xbf, 0xf2, + 0x63, 0x26, 0x43, 0xda, 0xdd, 0x40, 0xeb, 0xbf, 0xd4, 0xaf, 0x9a, 0xdf, 0xfb, 0xe6, 0xa0, 0xb9, + 0x03, 0x99, 0xba, 0x23, 0xb4, 0x30, 0xbb, 0x76, 0x77, 0xae, 0xaf, 0x59, 0xd3, 0xc0, 0x0f, 0xff, + 0x18, 0x3a, 0x93, 0x4b, 0xa1, 0xff, 0x7e, 0x52, 0x62, 0xf7, 0xb7, 0x14, 0x75, 0xb8, 0x7f, 0xff, + 0xaf, 0xe0, 0x55, 0x55, 0x7f, 0xfe, 0xb5, 0xbe, 0x5a, 0x83, 0xe8, 0xec, 0x22, 0x70, 0xce, 0x2f, + 0x02, 0xe7, 0xeb, 0x45, 0xe0, 0xbc, 0xbf, 0x0c, 0x1a, 0xe7, 0x97, 0x41, 0xe3, 0xf3, 0x65, 0xd0, + 0x78, 0xfa, 0x30, 0x65, 0x6a, 0x34, 0x49, 0x30, 0x15, 0x19, 0xb1, 0x8f, 0x9e, 0x25, 0x74, 0x37, + 0x15, 0x64, 0x1a, 0xde, 0x25, 0x99, 0x18, 0x4e, 0xc6, 0x20, 0xf5, 0x4b, 0xae, 0xbd, 0x60, 0xf5, + 0x32, 0x07, 0x99, 0xb4, 0xcc, 0xe3, 0xbd, 0xf7, 0x3d, 0x00, 0x00, 0xff, 0xff, 0xc9, 0x87, 0xc2, + 0x5c, 0xb3, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -403,6 +406,13 @@ func (m *MsgTransfer) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Encoding) > 0 { + i -= len(m.Encoding) + copy(dAtA[i:], m.Encoding) + i = encodeVarintTx(dAtA, i, uint64(len(m.Encoding))) + i-- + dAtA[i] = 0x4a + } if len(m.Memo) > 0 { i -= len(m.Memo) copy(dAtA[i:], m.Memo) @@ -601,6 +611,10 @@ func (m *MsgTransfer) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + l = len(m.Encoding) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -920,6 +934,38 @@ func (m *MsgTransfer) Unmarshal(dAtA []byte) error { } m.Memo = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Encoding", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Encoding = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/modules/apps/transfer/v2/ibc_module_test.go b/modules/apps/transfer/v2/ibc_module_test.go index 081df30683a..1563f72dd75 100644 --- a/modules/apps/transfer/v2/ibc_module_test.go +++ b/modules/apps/transfer/v2/ibc_module_test.go @@ -13,6 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" ibctesting "github.com/cosmos/ibc-go/v10/testing" @@ -202,6 +203,15 @@ func (suite *TransferTestSuite) TestOnRecvPacket() { suite.Require().True(ok) originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount) + msg := types.NewMsgTransferWithEncoding( + types.PortID, suite.pathAToB.EndpointA.ClientID, + originalCoin, suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, + timeoutTimestamp, "", types.EncodingProtobuf, + ) + _, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + token, err := suite.chainA.GetSimApp().TransferKeeper.TokenFromCoin(suite.chainA.GetContext(), originalCoin) suite.Require().NoError(err) @@ -217,15 +227,6 @@ func (suite *TransferTestSuite) TestOnRecvPacket() { types.PortID, types.PortID, types.V1, types.EncodingProtobuf, bz, ) - msg := channeltypesv2.NewMsgSendPacket( - suite.pathAToB.EndpointA.ClientID, - timeoutTimestamp, - suite.chainA.SenderAccount.GetAddress().String(), - payload, - ) - - _, err = suite.chainA.SendMsgs(msg) - suite.Require().NoError(err) // message committed ctx := suite.chainB.GetContext() cbs := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) @@ -289,6 +290,15 @@ func (suite *TransferTestSuite) TestOnAckPacket() { suite.Require().True(ok) originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount) + msg := types.NewMsgTransferWithEncoding( + types.PortID, suite.pathAToB.EndpointA.ClientID, + originalCoin, suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, + timeoutTimestamp, "", types.EncodingProtobuf, + ) + _, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + token, err := suite.chainA.GetSimApp().TransferKeeper.TokenFromCoin(suite.chainA.GetContext(), originalCoin) suite.Require().NoError(err) @@ -304,15 +314,6 @@ func (suite *TransferTestSuite) TestOnAckPacket() { types.PortID, types.PortID, types.V1, types.EncodingProtobuf, bz, ) - msg := channeltypesv2.NewMsgSendPacket( - suite.pathAToB.EndpointA.ClientID, - timeoutTimestamp, - suite.chainA.SenderAccount.GetAddress().String(), - payload, - ) - - _, err = suite.chainA.SendMsgs(msg) - suite.Require().NoError(err) // message committed ctx := suite.chainA.GetContext() cbs := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) @@ -386,6 +387,15 @@ func (suite *TransferTestSuite) TestOnTimeoutPacket() { suite.Require().True(ok) originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount) + msg := types.NewMsgTransferWithEncoding( + types.PortID, suite.pathAToB.EndpointA.ClientID, + originalCoin, suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, + timeoutTimestamp, "", types.EncodingProtobuf, + ) + _, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + token, err := suite.chainA.GetSimApp().TransferKeeper.TokenFromCoin(suite.chainA.GetContext(), originalCoin) suite.Require().NoError(err) @@ -401,15 +411,6 @@ func (suite *TransferTestSuite) TestOnTimeoutPacket() { types.PortID, types.PortID, types.V1, types.EncodingProtobuf, bz, ) - msg := channeltypesv2.NewMsgSendPacket( - suite.pathAToB.EndpointA.ClientID, - timeoutTimestamp, - suite.chainA.SenderAccount.GetAddress().String(), - payload, - ) - - _, err = suite.chainA.SendMsgs(msg) - suite.Require().NoError(err) // message committed // on successful send, the tokens sent in packets should be in escrow escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID) diff --git a/modules/core/04-channel/v2/keeper/msg_server.go b/modules/core/04-channel/v2/keeper/msg_server.go index c649278ea95..a16ba23afc7 100644 --- a/modules/core/04-channel/v2/keeper/msg_server.go +++ b/modules/core/04-channel/v2/keeper/msg_server.go @@ -3,7 +3,6 @@ package keeper import ( "bytes" "context" - "time" errorsmod "cosmossdk.io/errors" @@ -20,18 +19,6 @@ var _ types.MsgServer = &Keeper{} func (k *Keeper) SendPacket(goCtx context.Context, msg *types.MsgSendPacket) (*types.MsgSendPacketResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // Note, the validate basic function in sendPacket does the timeoutTimestamp != 0 check and other stateless checks on the packet. - // timeoutTimestamp must be greater than current block time - timeout := time.Unix(int64(msg.TimeoutTimestamp), 0) - if timeout.Before(ctx.BlockTime()) { - return nil, errorsmod.Wrap(types.ErrTimeoutElapsed, "timeout is less than the current block timestamp") - } - - // timeoutTimestamp must be less than current block time + MaxTimeoutDelta - if timeout.After(ctx.BlockTime().Add(types.MaxTimeoutDelta)) { - return nil, errorsmod.Wrap(types.ErrInvalidTimeout, "timeout exceeds the maximum expected value") - } - signer, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { ctx.Logger().Error("send packet failed", "error", errorsmod.Wrap(err, "invalid address for msg Signer")) diff --git a/modules/core/04-channel/v2/keeper/packet.go b/modules/core/04-channel/v2/keeper/packet.go index 8ae57dd7f62..34c10f94f82 100644 --- a/modules/core/04-channel/v2/keeper/packet.go +++ b/modules/core/04-channel/v2/keeper/packet.go @@ -30,6 +30,18 @@ func (k *Keeper) sendPacket( return 0, "", errorsmod.Wrapf(clientv2types.ErrCounterpartyNotFound, "counterparty not found for client: %s", sourceClient) } + // Note, the validate basic function in sendPacket does the timeoutTimestamp != 0 check and other stateless checks on the packet. + // timeoutTimestamp must be greater than current block time + timeout := time.Unix(int64(timeoutTimestamp), 0) + if timeout.Before(ctx.BlockTime()) { + return 0, "", errorsmod.Wrap(types.ErrTimeoutElapsed, "timeout is less than the current block timestamp") + } + + // timeoutTimestamp must be less than current block time + MaxTimeoutDelta + if timeout.After(ctx.BlockTime().Add(types.MaxTimeoutDelta)) { + return 0, "", errorsmod.Wrap(types.ErrInvalidTimeout, "timeout exceeds the maximum expected value") + } + sequence, found := k.GetNextSequenceSend(ctx, sourceClient) if !found { return 0, "", errorsmod.Wrapf(types.ErrSequenceSendNotFound, "source client: %s", sourceClient) diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go index 5f58e3d1f9d..125d3f729b8 100644 --- a/modules/light-clients/08-wasm/testing/simapp/app.go +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -488,6 +488,7 @@ func NewSimApp( appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeperV2, app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/proto/ibc/applications/transfer/v1/tx.proto b/proto/ibc/applications/transfer/v1/tx.proto index 8e43d1d86e5..eefeb0bc678 100644 --- a/proto/ibc/applications/transfer/v1/tx.proto +++ b/proto/ibc/applications/transfer/v1/tx.proto @@ -49,6 +49,8 @@ message MsgTransfer { uint64 timeout_timestamp = 7; // optional memo string memo = 8; + // optional encoding + string encoding = 9; } // MsgTransferResponse defines the Msg/Transfer response type. diff --git a/simapp/app.go b/simapp/app.go index da8151a9b16..985e29ebe2a 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -410,6 +410,7 @@ func NewSimApp( appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeperV2, app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 7ce4749bb0f..e59e902453b 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -390,6 +390,7 @@ func NewSimApp( appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeperV2, app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), )