Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MsgTransfer For Eureka #7957

Merged
merged 13 commits into from
Feb 24, 2025
2 changes: 2 additions & 0 deletions modules/apps/callbacks/testing/simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ 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.IBCKeeper.ClientV2Keeper,
app.AccountKeeper, app.BankKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
Expand Down
30 changes: 18 additions & 12 deletions modules/apps/transfer/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ 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
clientKeeperV2 types.ClientKeeperV2
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could remove this if desired

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have less stuff to put in here, but what would that mean for the code?

AuthKeeper types.AccountKeeper
BankKeeper types.BankKeeper

// the address capable of executing a MsgUpdateParams message. Typically, this
// should be the x/gov module account.
Expand All @@ -46,6 +48,8 @@ func NewKeeper(
legacySubspace types.ParamSubspace,
ics4Wrapper porttypes.ICS4Wrapper,
channelKeeper types.ChannelKeeper,
channelKeeperV2 types.ChannelKeeperV2,
clientKeeperV2 types.ClientKeeperV2,
authKeeper types.AccountKeeper,
bankKeeper types.BankKeeper,
authority string,
Expand All @@ -60,14 +64,16 @@ 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,
clientKeeperV2: clientKeeperV2,
AuthKeeper: authKeeper,
BankKeeper: bankKeeper,
authority: authority,
}
}

Expand Down
6 changes: 6 additions & 0 deletions modules/apps/transfer/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ 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.App.GetIBCKeeper().ClientV2Keeper,
suite.chainA.GetSimApp().AccountKeeper,
suite.chainA.GetSimApp().BankKeeper,
suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(),
Expand All @@ -72,6 +74,8 @@ 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.App.GetIBCKeeper().ClientV2Keeper,
authkeeper.AccountKeeper{}, // empty account keeper
suite.chainA.GetSimApp().BankKeeper,
suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(),
Expand All @@ -84,6 +88,8 @@ 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.App.GetIBCKeeper().ClientV2Keeper,
suite.chainA.GetSimApp().AccountKeeper,
suite.chainA.GetSimApp().BankKeeper,
"", // authority
Expand Down
97 changes: 75 additions & 22 deletions modules/apps/transfer/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ 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"
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"
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"
ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors"
)

Expand All @@ -29,16 +32,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.
Expand All @@ -54,33 +47,93 @@ 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 eureka counterparty exists with source channel, then use IBC V2 protocol
// otherwise use IBC V1 protocol
var (
channel channeltypes.Channel
counterparty clienttypesv2.CounterpartyInfo
destinationPort string
destinationChannel string
isIBCV2 bool
)
if err != nil {
return nil, err
counterparty, isIBCV2 = k.clientKeeperV2.GetClientCounterparty(ctx, msg.SourceChannel)
if !isIBCV2 {
var found bool
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)
}
}

sequence, err := k.ics4Wrapper.SendPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packetDataBytes)
var sequence uint64
if isIBCV2 {
sequence, err = k.transferV2Packet(ctx, msg.Encoding, msg.SourceChannel, msg.TimeoutTimestamp, packetData)
destinationPort = types.PortID
destinationChannel = counterparty.ClientId
} else {
sequence, err = k.transferV1Packet(ctx, msg.SourceChannel, token, msg.TimeoutHeight, msg.TimeoutTimestamp, packetData)
destinationPort = channel.Counterparty.PortId
destinationChannel = channel.Counterparty.ChannelId
}
if err != nil {
return nil, err
}

events.EmitTransferEvent(ctx, sender.String(), msg.Receiver, token, msg.Memo)

destinationPort := channel.Counterparty.PortId
destinationChannel := channel.Counterparty.ChannelId
telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, destinationPort, destinationChannel, token)
telemetry.ReportTransfer(types.PortID, msg.SourceChannel, destinationPort, destinationChannel, token)

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 0, err
}

events.EmitTransferEvent(ctx, packetData.Sender, packetData.Receiver, token, packetData.Memo)

return sequence, nil
}

func (k Keeper) transferV2Packet(ctx sdk.Context, encoding, sourceChannel string, timeoutTimestamp uint64, packetData types.FungibleTokenPacketData) (uint64, error) {
if encoding == "" {
encoding = types.EncodingJSON
}

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.
func (k Keeper) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) {
if k.GetAuthority() != msg.Signer {
Expand Down
147 changes: 147 additions & 0 deletions modules/apps/transfer/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -11,6 +12,7 @@ import (
abci "github.com/cometbft/cometbft/abci/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"
ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors"
ibctesting "github.com/cosmos/ibc-go/v10/testing"
Expand Down Expand Up @@ -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"
},
channeltypes.ErrChannelNotFound,
},
}

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()
Expand Down
Loading
Loading