diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go index bca2274b81a..9922ea456fd 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -11,6 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/v9/modules/apps/transfer/internal/telemetry" "github.com/cosmos/ibc-go/v9/modules/apps/transfer/keeper" "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types" @@ -171,12 +172,11 @@ func (im IBCModule) OnRecvPacket( relayer sdk.AccAddress, ) ibcexported.Acknowledgement { var ( + ack ibcexported.Acknowledgement ackErr error data types.FungibleTokenPacketDataV2 ) - ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) - // we are explicitly wrapping this emit event call in an anonymous function so that // the packet data is evaluated after it has been assigned a value. defer func() { @@ -192,12 +192,36 @@ func (im IBCModule) OnRecvPacket( return ack } - if ackErr = im.keeper.OnRecvPacket(ctx, packet, data); ackErr != nil { + receivedCoins, ackErr := im.keeper.OnRecvPacket( + ctx, + data, + packet.SourcePort, + packet.SourceChannel, + packet.DestinationPort, + packet.DestinationChannel, + ) + if ackErr != nil { ack = channeltypes.NewErrorAcknowledgement(ackErr) im.keeper.Logger.Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) return ack } + if data.HasForwarding() { + // we are now sending from the forward escrow address to the final receiver address. + if ackErr = im.keeper.ForwardPacket(ctx, data, packet, receivedCoins); ackErr != nil { + ack = channeltypes.NewErrorAcknowledgement(ackErr) + im.keeper.Logger.Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) + return ack + + } + + ack = nil + } + + ack = channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + telemetry.ReportOnRecvPacket(packet, data.Tokens) + im.keeper.Logger.Info("successfully handled ICS-20 packet", "sequence", packet.Sequence) if data.HasForwarding() { @@ -227,10 +251,16 @@ func (im IBCModule) OnAcknowledgementPacket( return err } - if err := im.keeper.OnAcknowledgementPacket(ctx, packet, data, ack); err != nil { + if err := im.keeper.OnAcknowledgementPacket(ctx, packet.SourcePort, packet.SourceChannel, data, ack); err != nil { return err } + if forwardedPacket, isForwarded := im.keeper.GetForwardedPacket(ctx, packet.SourcePort, packet.SourceChannel, packet.Sequence); isForwarded { + if err := im.keeper.HandleForwardedPacketAcknowledgement(ctx, packet, forwardedPacket, data, ack); err != nil { + return err + } + } + return im.keeper.EmitOnAcknowledgementPacketEvent(ctx, data, ack) } @@ -247,10 +277,16 @@ func (im IBCModule) OnTimeoutPacket( } // refund tokens - if err := im.keeper.OnTimeoutPacket(ctx, packet, data); err != nil { + if err := im.keeper.OnTimeoutPacket(ctx, packet.SourcePort, packet.SourceChannel, data); err != nil { return err } + if forwardedPacket, isForwarded := im.keeper.GetForwardedPacket(ctx, packet.SourcePort, packet.SourceChannel, packet.Sequence); isForwarded { + if err := im.keeper.HandleForwardedPacketTimeout(ctx, packet, forwardedPacket, data); err != nil { + return err + } + } + return im.keeper.EmitOnTimeoutEvent(ctx, data) } diff --git a/modules/apps/transfer/keeper/events.go b/modules/apps/transfer/keeper/events.go index c30fdaabd9f..312454a6359 100644 --- a/modules/apps/transfer/keeper/events.go +++ b/modules/apps/transfer/keeper/events.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v9/modules/core/exported" ) // EmitTransferEvent emits an ibc transfer event on successful transfers. @@ -36,7 +37,7 @@ func (k Keeper) EmitTransferEvent(ctx context.Context, sender, receiver string, } // EmitOnRecvPacketEvent emits a fungible token packet event in the OnRecvPacket callback -func (k Keeper) EmitOnRecvPacketEvent(ctx context.Context, packetData types.FungibleTokenPacketDataV2, ack channeltypes.Acknowledgement, ackErr error) error { +func (k Keeper) EmitOnRecvPacketEvent(ctx context.Context, packetData types.FungibleTokenPacketDataV2, ack ibcexported.Acknowledgement, ackErr error) error { tokensStr := mustMarshalJSON(packetData.Tokens) forwardingHopStr := mustMarshalJSON(packetData.Forwarding.Hops) diff --git a/modules/apps/transfer/keeper/export_test.go b/modules/apps/transfer/keeper/export_test.go index dedfd560b9b..7aa98f74e9b 100644 --- a/modules/apps/transfer/keeper/export_test.go +++ b/modules/apps/transfer/keeper/export_test.go @@ -39,11 +39,6 @@ func (k Keeper) UnwindHops(ctx sdk.Context, msg *types.MsgTransfer) (*types.MsgT return k.unwindHops(ctx, msg) } -// GetForwardedPacket is a wrapper around getForwardedPacket for testing purposes. -func (k Keeper) GetForwardedPacket(ctx sdk.Context, portID, channelID string, sequence uint64) (channeltypes.Packet, bool) { - return k.getForwardedPacket(ctx, portID, channelID, sequence) -} - // SetForwardedPacket is a wrapper around setForwardedPacket for testing purposes. func (k Keeper) SetForwardedPacket(ctx sdk.Context, portID, channelID string, sequence uint64, packet channeltypes.Packet) { k.setForwardedPacket(ctx, portID, channelID, sequence, packet) diff --git a/modules/apps/transfer/keeper/forwarding.go b/modules/apps/transfer/keeper/forwarding.go index e0232caf4aa..fba9748d8d2 100644 --- a/modules/apps/transfer/keeper/forwarding.go +++ b/modules/apps/transfer/keeper/forwarding.go @@ -13,8 +13,8 @@ import ( ibcerrors "github.com/cosmos/ibc-go/v9/modules/core/errors" ) -// forwardPacket forwards a fungible FungibleTokenPacketDataV2 to the next hop in the forwarding path. -func (k Keeper) forwardPacket(ctx context.Context, data types.FungibleTokenPacketDataV2, packet channeltypes.Packet, receivedCoins sdk.Coins) error { +// ForwardPacket forwards a fungible FungibleTokenPacketDataV2 to the next hop in the forwarding path. +func (k Keeper) ForwardPacket(ctx context.Context, data types.FungibleTokenPacketDataV2, packet channeltypes.Packet, receivedCoins sdk.Coins) error { var nextForwardingPath *types.Forwarding if len(data.Forwarding.Hops) > 1 { // remove the first hop since we are going to send to the first hop now and we want to propagate the rest of the hops to the receiver diff --git a/modules/apps/transfer/keeper/keeper.go b/modules/apps/transfer/keeper/keeper.go index ee6442b0ae9..0b54e84957f 100644 --- a/modules/apps/transfer/keeper/keeper.go +++ b/modules/apps/transfer/keeper/keeper.go @@ -308,7 +308,7 @@ func (k Keeper) setForwardedPacket(ctx context.Context, portID, channelID string } // getForwardedPacket gets the forwarded packet from the store. -func (k Keeper) getForwardedPacket(ctx context.Context, portID, channelID string, sequence uint64) (channeltypes.Packet, bool) { +func (k Keeper) GetForwardedPacket(ctx context.Context, portID, channelID string, sequence uint64) (channeltypes.Packet, bool) { store := k.KVStoreService.OpenKVStore(ctx) bz, err := store.Get(types.PacketForwardKey(portID, channelID, sequence)) if err != nil { diff --git a/modules/apps/transfer/keeper/mbt_relay_test.go b/modules/apps/transfer/keeper/mbt_relay_test.go index c06b625c5d9..8174b1960f5 100644 --- a/modules/apps/transfer/keeper/mbt_relay_test.go +++ b/modules/apps/transfer/keeper/mbt_relay_test.go @@ -365,20 +365,27 @@ func (suite *KeeperTestSuite) TestModelBasedRelay() { } case "OnRecvPacket": - err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket(suite.chainB.GetContext(), packet, tc.packet.Data) + _, err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainB.GetContext(), + tc.packet.Data, + packet.SourcePort, + packet.SourceChannel, + packet.DestinationPort, + packet.DestinationChannel, + ) case "OnTimeoutPacket": registerDenomFn() - err = suite.chainB.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainB.GetContext(), packet, tc.packet.Data) + err = suite.chainB.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, tc.packet.Data) case "OnRecvAcknowledgementResult": err = suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket( - suite.chainB.GetContext(), packet, tc.packet.Data, + suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, tc.packet.Data, channeltypes.NewResultAcknowledgement(nil)) case "OnRecvAcknowledgementError": registerDenomFn() err = suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket( - suite.chainB.GetContext(), packet, tc.packet.Data, + suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, tc.packet.Data, channeltypes.NewErrorAcknowledgement(fmt.Errorf("MBT Error Acknowledgement"))) default: err = fmt.Errorf("Unknown handler: %s", tc.handler) diff --git a/modules/apps/transfer/keeper/msg_server.go b/modules/apps/transfer/keeper/msg_server.go index 1fab80dfa94..034c2c078e0 100644 --- a/modules/apps/transfer/keeper/msg_server.go +++ b/modules/apps/transfer/keeper/msg_server.go @@ -8,7 +8,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/v9/modules/apps/transfer/internal/telemetry" "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types" ibcerrors "github.com/cosmos/ibc-go/v9/modules/core/errors" ) @@ -25,30 +27,70 @@ func (k Keeper) Transfer(ctx context.Context, msg *types.MsgTransfer) (*types.Ms return nil, err } - coins := msg.GetCoins() + if msg.Forwarding.GetUnwind() { + msg, err = k.unwindHops(ctx, msg) + if err != nil { + return nil, err + } + } - if err := k.BankKeeper.IsSendEnabledCoins(ctx, coins...); err != nil { - return nil, errorsmod.Wrap(types.ErrSendDisabled, err.Error()) + 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) } - if k.IsBlockedAddr(sender) { - return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to send funds", sender) + 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) } - if msg.Forwarding.GetUnwind() { - msg, err = k.unwindHops(ctx, msg) + coins := msg.GetCoins() + hops := msg.Forwarding.GetHops() + if appVersion == types.V1 { + // ics20-1 only supports a single coin, so if that is the current version, we must only process a single coin. + if len(coins) > 1 { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "cannot transfer multiple coins with %s", types.V1) + } + + // ics20-1 does not support forwarding, so if that is the current version, we must reject the transfer. + if len(hops) > 0 { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "cannot forward coins with %s", types.V1) + } + } + + tokens := make([]types.Token, len(coins)) + + for i, coin := range coins { + tokens[i], err = k.tokenFromCoin(ctx, coin) if err != nil { return nil, err } } - sequence, err := k.sendTransfer( - ctx, msg.SourcePort, msg.SourceChannel, coins, sender, msg.Receiver, msg.TimeoutHeight, msg.TimeoutTimestamp, - msg.Memo, msg.Forwarding.GetHops()) + if err := k.SendTransfer(ctx, msg.SourcePort, msg.SourceChannel, tokens, sender); err != nil { + return nil, err + } + + packetDataBytes, err := createPacketDataBytesFromVersion( + appVersion, sender.String(), msg.Receiver, msg.Memo, tokens, hops, + ) if err != nil { return nil, err } + sequence, err := k.ics4Wrapper.SendPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packetDataBytes) + if err != nil { + return nil, err + } + + if err := k.EmitTransferEvent(ctx, sender.String(), msg.Receiver, tokens, msg.Memo, hops); err != nil { + return nil, err + } + + destinationPort := channel.Counterparty.PortId + destinationChannel := channel.Counterparty.ChannelId + telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, destinationPort, destinationChannel, tokens) + k.Logger.Info("IBC fungible token transfer", "tokens", coins, "sender", msg.Sender, "receiver", msg.Receiver) return &types.MsgTransferResponse{Sequence: sequence}, nil diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index bfb2adfc73a..5d90f112792 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -10,15 +10,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/v9/modules/apps/transfer/internal/telemetry" internaltypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/internal/types" "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types" ibcerrors "github.com/cosmos/ibc-go/v9/modules/core/errors" ) -// sendTransfer handles transfer sending logic. There are 2 possible cases: +// SendTransfer handles transfer sending logic. There are 2 possible cases: // // 1. Sender chain is acting as the source zone. The coins are transferred // to an escrow address (i.e locked) on the sender chain and then transferred @@ -50,62 +48,39 @@ import ( // 4. A -> C : sender chain is sink zone. Denom upon receiving: 'C/B/denom' // 5. C -> B : sender chain is sink zone. Denom upon receiving: 'B/denom' // 6. B -> A : sender chain is sink zone. Denom upon receiving: 'denom' -func (k Keeper) sendTransfer( +func (k Keeper) SendTransfer( ctx context.Context, - sourcePort, + sourcePort string, sourceChannel string, - coins sdk.Coins, + tokens types.Tokens, sender sdk.AccAddress, - receiver string, - timeoutHeight clienttypes.Height, - timeoutTimestamp uint64, - memo string, - hops []types.Hop, -) (uint64, error) { - channel, found := k.channelKeeper.GetChannel(ctx, sourcePort, sourceChannel) - if !found { - return 0, errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", sourcePort, sourceChannel) +) error { + if !k.GetParams(ctx).SendEnabled { + return types.ErrSendDisabled } - appVersion, found := k.ics4Wrapper.GetAppVersion(ctx, sourcePort, sourceChannel) - if !found { - return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "application version not found for source port: %s and source channel: %s", sourcePort, sourceChannel) + if k.IsBlockedAddr(sender) { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to send funds", sender) } - if appVersion == types.V1 { - // ics20-1 only supports a single coin, so if that is the current version, we must only process a single coin. - if len(coins) > 1 { - return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "cannot transfer multiple coins with %s", types.V1) + for _, token := range tokens { + coin, err := token.ToCoin() + if err != nil { + return err } - // ics20-1 does not support forwarding, so if that is the current version, we must reject the transfer. - if len(hops) > 0 { - return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "cannot forward coins with %s", types.V1) + if err := k.BankKeeper.IsSendEnabledCoins(ctx, coin); err != nil { + return errorsmod.Wrap(types.ErrSendDisabled, err.Error()) } - } - - destinationPort := channel.Counterparty.PortId - destinationChannel := channel.Counterparty.ChannelId - - // begin createOutgoingPacket logic - // See spec for this logic: https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay - tokens := make([]types.Token, 0, len(coins)) - - for _, coin := range coins { // Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom. if coin.Amount.Equal(types.UnboundedSpendLimit()) { coin.Amount = k.BankKeeper.SpendableCoin(ctx, sender, coin.Denom).Amount if coin.Amount.IsZero() { - return 0, errorsmod.Wrapf(types.ErrInvalidAmount, "empty spendable balance for %s", coin.Denom) + return errorsmod.Wrapf(types.ErrInvalidAmount, "empty spendable balance for %s", coin.Denom) } } - token, err := k.tokenFromCoin(ctx, coin) - if err != nil { - return 0, err - } - // NOTE: SendTransfer simply sends the denomination as it exists on its own // chain inside the packet data. The receiving chain will perform denom // prefixing as necessary. @@ -117,7 +92,7 @@ func (k Keeper) sendTransfer( if err := k.BankKeeper.SendCoinsFromAccountToModule( ctx, sender, types.ModuleName, sdk.NewCoins(coin), ); err != nil { - return 0, err + return err } if err := k.BankKeeper.BurnCoins( @@ -132,30 +107,14 @@ func (k Keeper) sendTransfer( // obtain the escrow address for the source channel end escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) if err := k.EscrowCoin(ctx, sender, escrowAddress, coin); err != nil { - return 0, err + return err } } tokens = append(tokens, token) } - packetDataBytes, err := createPacketDataBytesFromVersion(appVersion, sender.String(), receiver, memo, tokens, hops) - if err != nil { - return 0, err - } - - sequence, err := k.ics4Wrapper.SendPacket(ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, packetDataBytes) - if err != nil { - return 0, err - } - - if err := k.EmitTransferEvent(ctx, sender.String(), receiver, tokens, memo, hops); err != nil { - return 0, err - } - - telemetry.ReportTransfer(sourcePort, sourceChannel, destinationPort, destinationChannel, tokens) - - return sequence, nil + return nil } // OnRecvPacket processes a cross chain fungible token transfer. @@ -167,23 +126,30 @@ func (k Keeper) sendTransfer( // // In the case of packet forwarding, the packet is sent on the next hop as specified // in the packet's ForwardingPacketData. -func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, data types.FungibleTokenPacketDataV2) error { +func (k Keeper) OnRecvPacket( + ctx context.Context, + data types.FungibleTokenPacketDataV2, + sourcePort string, + sourceChannel string, + destPort string, + destChannel string, +) (sdk.Coins, error) { // validate packet data upon receiving if err := data.ValidateBasic(); err != nil { - return errorsmod.Wrapf(err, "error validating ICS-20 transfer packet data") + return nil, errorsmod.Wrapf(err, "error validating ICS-20 transfer packet data") } if !k.GetParams(ctx).ReceiveEnabled { - return types.ErrReceiveDisabled + return nil, types.ErrReceiveDisabled } receiver, err := k.getReceiverFromPacketData(data) if err != nil { - return err + return nil, err } if k.IsBlockedAddr(receiver) { - return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to receive funds", receiver) + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to receive funds", receiver) } receivedCoins := make(sdk.Coins, 0, len(data.Tokens)) @@ -191,7 +157,7 @@ func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, da // parse the transfer amount transferAmount, ok := sdkmath.NewIntFromString(token.Amount) if !ok { - return errorsmod.Wrapf(types.ErrInvalidAmount, "unable to parse transfer amount: %s", token.Amount) + return nil, errorsmod.Wrapf(types.ErrInvalidAmount, "unable to parse transfer amount: %s", token.Amount) } // This is the prefix that would have been prefixed to the denomination @@ -201,7 +167,7 @@ func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, da // NOTE: We use SourcePort and SourceChannel here, because the counterparty // chain would have prefixed with DestPort and DestChannel when originally // receiving this token. - if token.Denom.HasPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) { + if token.Denom.HasPrefix(sourcePort, sourceChannel) { // sender chain is not the source, unescrow tokens // remove prefix added by sender chain @@ -209,9 +175,9 @@ func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, da coin := sdk.NewCoin(token.Denom.IBCDenom(), transferAmount) - escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) + escrowAddress := types.GetEscrowAddress(destPort, destChannel) if err := k.UnescrowCoin(ctx, escrowAddress, receiver, coin); err != nil { - return err + return nil, err } // Appending token. The new denom has been computed @@ -220,7 +186,7 @@ func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, da // sender chain is the source, mint vouchers // since SendPacket did not prefix the denomination, we must add the destination port and channel to the trace - trace := []types.Hop{types.NewHop(packet.DestinationPort, packet.DestinationChannel)} + trace := []types.Hop{types.NewHop(destPort, destChannel)} token.Denom.Trace = append(trace, token.Denom.Trace...) if !k.HasDenom(ctx, token.Denom.Hash()) { @@ -233,7 +199,7 @@ func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, da } if err := k.EmitDenomEvent(ctx, token); err != nil { - return err + return nil, err } voucher := sdk.NewCoin(voucherDenom, transferAmount) @@ -242,7 +208,7 @@ func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, da if err := k.BankKeeper.MintCoins( ctx, types.ModuleName, sdk.NewCoins(voucher), ); err != nil { - return errorsmod.Wrap(err, "failed to mint IBC tokens") + return nil, errorsmod.Wrap(err, "failed to mint IBC tokens") } // send to receiver @@ -250,102 +216,109 @@ func (k Keeper) OnRecvPacket(ctx context.Context, packet channeltypes.Packet, da if err := k.BankKeeper.SendCoins( ctx, moduleAddr, receiver, sdk.NewCoins(voucher), ); err != nil { - return errorsmod.Wrapf(err, "failed to send coins to receiver %s", receiver.String()) + return nil, errorsmod.Wrapf(err, "failed to send coins to receiver %s", receiver.String()) } receivedCoins = append(receivedCoins, voucher) } } - if data.HasForwarding() { - // we are now sending from the forward escrow address to the final receiver address. - if err := k.forwardPacket(ctx, data, packet, receivedCoins); err != nil { - return err - } - } - - telemetry.ReportOnRecvPacket(packet, data.Tokens) - // The ibc_module.go module will return the proper ack. - return nil + return receivedCoins, nil } // OnAcknowledgementPacket responds to the success or failure of a packet acknowledgment // written on the receiving chain. // -// If no forwarding occurs and the acknowledgement was a success then nothing occurs. Otherwise, +// If the acknowledgement was a success then nothing occurs. Otherwise, // if the acknowledgement failed, then the sender is refunded their tokens. -// -// If forwarding is used and the acknowledgement was a success, a successful acknowledgement is written -// for the forwarded packet. Otherwise, if the acknowledgement failed, after refunding the sender, the -// tokens of the forwarded packet that were received are in turn either refunded or burned. -func (k Keeper) OnAcknowledgementPacket(ctx context.Context, packet channeltypes.Packet, data types.FungibleTokenPacketDataV2, ack channeltypes.Acknowledgement) error { - forwardedPacket, isForwarded := k.getForwardedPacket(ctx, packet.SourcePort, packet.SourceChannel, packet.Sequence) - +func (k Keeper) OnAcknowledgementPacket( + ctx context.Context, + sourcePort string, + sourceChannel string, + data types.FungibleTokenPacketDataV2, + ack channeltypes.Acknowledgement, +) error { switch ack.Response.(type) { case *channeltypes.Acknowledgement_Result: - if isForwarded { - // Write a successful async ack for the forwardedPacket - forwardAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) - return k.acknowledgeForwardedPacket(ctx, forwardedPacket, packet, forwardAck) - } - // the acknowledgement succeeded on the receiving chain so nothing // needs to be executed and no error needs to be returned return nil case *channeltypes.Acknowledgement_Error: - // We refund the tokens from the escrow address to the sender - if err := k.refundPacketTokens(ctx, packet, data); err != nil { + if err := k.refundPacketTokens(ctx, sourcePort, sourceChannel, data); err != nil { return err } - if isForwarded { - // the forwarded packet has failed, thus the funds have been refunded to the intermediate address. - // we must revert the changes that came from successfully receiving the tokens on our chain - // before propagating the error acknowledgement back to original sender chain - if err := k.revertForwardedPacket(ctx, forwardedPacket, data); err != nil { - return err - } - - forwardAck := internaltypes.NewForwardErrorAcknowledgement(packet, ack) - return k.acknowledgeForwardedPacket(ctx, forwardedPacket, packet, forwardAck) - } - return nil default: return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected one of [%T, %T], got %T", channeltypes.Acknowledgement_Result{}, channeltypes.Acknowledgement_Error{}, ack.Response) } } -// OnTimeoutPacket processes a transfer packet timeout. +// HandleForwardedPacketAcknowledgement processes an acknowledgement for a packet that was sent from the chain as an intermediate. // -// If no forwarding occurs, it refunds the tokens to the sender. -// -// If forwarding is used and the chain acted as a middle hop on a multihop transfer, after refunding -// the tokens to the sender, the tokens of the forwarded packet that were received are in turn -// either refunded or burned. -func (k Keeper) OnTimeoutPacket(ctx context.Context, packet channeltypes.Packet, data types.FungibleTokenPacketDataV2) error { - if err := k.refundPacketTokens(ctx, packet, data); err != nil { - return err - } +// If the acknowledgement was a success, a successful acknowledgement is written +// for the forwarded packet. Otherwise, if the acknowledgement failed, after refunding the sender, the +// tokens of the forwarded packet that were received are in turn either refunded or burned. +func (k Keeper) HandleForwardedPacketAcknowledgement( + ctx context.Context, + packet channeltypes.Packet, + forwardedPacket channeltypes.Packet, + data types.FungibleTokenPacketDataV2, + ack channeltypes.Acknowledgement, +) error { + var forwardAck channeltypes.Acknowledgement - forwardedPacket, isForwarded := k.getForwardedPacket(ctx, packet.SourcePort, packet.SourceChannel, packet.Sequence) - if isForwarded { + switch ack.Response.(type) { + case *channeltypes.Acknowledgement_Result: + // Write a successful async ack for the forwardedPacket + forwardAck = channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + case *channeltypes.Acknowledgement_Error: + // the forwarded packet has failed, thus the funds have been refunded to the intermediate address. + // we must revert the changes that came from successfully receiving the tokens on our chain + // before propagating the error acknowledgement back to original sender chain if err := k.revertForwardedPacket(ctx, forwardedPacket, data); err != nil { return err } - forwardAck := internaltypes.NewForwardTimeoutAcknowledgement(packet) - return k.acknowledgeForwardedPacket(ctx, forwardedPacket, packet, forwardAck) + forwardAck = internaltypes.NewForwardErrorAcknowledgement(packet, ack) + default: + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected one of [%T, %T], got %T", channeltypes.Acknowledgement_Result{}, channeltypes.Acknowledgement_Error{}, ack.Response) } - return nil + return k.acknowledgeForwardedPacket(ctx, forwardedPacket, packet, forwardAck) +} + +// OnTimeoutPacket processes a transfer packet timeout by refunding the tokens to the sender +func (k Keeper) OnTimeoutPacket( + ctx context.Context, + sourcePort string, + sourceChannel string, + data types.FungibleTokenPacketDataV2, +) error { + return k.refundPacketTokens(ctx, sourcePort, sourceChannel, data) +} + +// HandleForwardedTimeout processes a timeout packet that was sent from the chain as an intermediate. +// The packet is reverted and the tokens are refunded to the sender. +func (k Keeper) HandleForwardedPacketTimeout(ctx context.Context, packet channeltypes.Packet, forwardedPacket channeltypes.Packet, data types.FungibleTokenPacketDataV2) error { + if err := k.revertForwardedPacket(ctx, forwardedPacket, data); err != nil { + return err + } + + forwardAck := internaltypes.NewForwardTimeoutAcknowledgement(packet) + return k.acknowledgeForwardedPacket(ctx, forwardedPacket, packet, forwardAck) } // refundPacketTokens will unescrow and send back the tokens back to sender // if the sending chain was the source chain. Otherwise, the sent tokens // were burnt in the original send so new tokens are minted and sent to // the sending address. -func (k Keeper) refundPacketTokens(ctx context.Context, packet channeltypes.Packet, data types.FungibleTokenPacketDataV2) error { +func (k Keeper) refundPacketTokens( + ctx context.Context, + sourcePort string, + sourceChannel string, + data types.FungibleTokenPacketDataV2, +) error { // NOTE: packet data type already checked in handler.go sender, err := sdk.AccAddressFromBech32(data.Sender) @@ -357,7 +330,7 @@ func (k Keeper) refundPacketTokens(ctx context.Context, packet channeltypes.Pack } // escrow address for unescrowing tokens back to sender - escrowAddress := types.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel()) + escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) moduleAccountAddr := k.AuthKeeper.GetModuleAddress(types.ModuleName) for _, token := range data.Tokens { @@ -368,7 +341,7 @@ func (k Keeper) refundPacketTokens(ctx context.Context, packet channeltypes.Pack // if the token we must refund is prefixed by the source port and channel // then the tokens were burnt when the packet was sent and we must mint new tokens - if token.Denom.HasPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) { + if token.Denom.HasPrefix(sourcePort, sourceChannel) { // mint vouchers back to sender if err := k.BankKeeper.MintCoins( ctx, types.ModuleName, sdk.NewCoins(coin), diff --git a/modules/apps/transfer/keeper/relay_forwarding_test.go b/modules/apps/transfer/keeper/relay_forwarding_test.go index f3756c34f95..95a8787b58f 100644 --- a/modules/apps/transfer/keeper/relay_forwarding_test.go +++ b/modules/apps/transfer/keeper/relay_forwarding_test.go @@ -1055,18 +1055,22 @@ func (suite *ForwardingTestSuite) TestOnTimeoutPacketForwarding() { suite.Require().NoError(err) // message committed // parse the packet from result events and recv packet on chainB - packet, err := ibctesting.ParsePacketFromEvents(result.Events) + packetFromAToB, err := ibctesting.ParsePacketFromEvents(result.Events) suite.Require().NoError(err) - suite.Require().NotNil(packet) + suite.Require().NotNil(packetFromAToB) err = pathAtoB.EndpointB.UpdateClient() suite.Require().NoError(err) // Receive packet on B. - result, err = pathAtoB.EndpointB.RecvPacketWithResult(packet) + result, err = pathAtoB.EndpointB.RecvPacketWithResult(packetFromAToB) suite.Require().NoError(err) suite.Require().NotNil(result) + packetFromBToC, err := ibctesting.ParsePacketFromEvents(result.Events) + suite.Require().NoError(err) + suite.Require().NotNil(packetFromBToC) + err = pathBtoC.EndpointA.UpdateClient() suite.Require().NoError(err) @@ -1078,74 +1082,33 @@ func (suite *ForwardingTestSuite) TestOnTimeoutPacketForwarding() { suite.assertAmountOnChain(suite.chainB, escrow, amount, denomAB.IBCDenom()) // Check that forwarded packet exists - forwardedPacket, found := suite.chainB.GetSimApp().TransferKeeper.GetForwardedPacket(suite.chainB.GetContext(), pathBtoC.EndpointA.ChannelConfig.PortID, pathBtoC.EndpointA.ChannelID, packet.Sequence) + forwardedPacket, found := suite.chainB.GetSimApp().TransferKeeper.GetForwardedPacket(suite.chainB.GetContext(), pathBtoC.EndpointA.ChannelConfig.PortID, pathBtoC.EndpointA.ChannelID, packetFromAToB.Sequence) suite.Require().True(found, "Chain B has no forwarded packet") - suite.Require().Equal(packet, forwardedPacket, "ForwardedPacket stored in ChainB is not the same that was sent") - - address := suite.chainB.GetSimApp().AuthKeeper.GetModuleAddress(types.ModuleName).String() - data := types.NewFungibleTokenPacketDataV2( - []types.Token{ - { - Denom: types.NewDenom(sdk.DefaultBondDenom, types.NewHop(pathAtoB.EndpointB.ChannelConfig.PortID, pathAtoB.EndpointB.ChannelID)), - Amount: "100", - }, - }, - address, - receiver.GetAddress().String(), - "", ibctesting.EmptyForwardingPacketData, - ) - - packet = channeltypes.NewPacket( - data.GetBytes(), - 1, - pathBtoC.EndpointA.ChannelConfig.PortID, - pathBtoC.EndpointA.ChannelID, - pathBtoC.EndpointB.ChannelConfig.PortID, - pathBtoC.EndpointB.ChannelID, - packet.TimeoutHeight, - packet.TimeoutTimestamp) + suite.Require().Equal(packetFromAToB, forwardedPacket, "ForwardedPacket stored in ChainB is not the same that was sent") - cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(pathBtoC.EndpointA.ChannelConfig.PortID) - suite.Require().True(ok) + // Time out packet + suite.coordinator.IncrementTimeBy(time.Minute * 5) + err = pathBtoC.EndpointA.UpdateClient() + suite.Require().NoError(err) - // Trigger OnTimeoutPacket for chainB - err = cbs.OnTimeoutPacket(suite.chainB.GetContext(), pathBtoC.EndpointA.GetChannel().Version, packet, nil) + result, err = pathBtoC.EndpointA.TimeoutPacketWithResult(packetFromBToC) + suite.Require().NoError(err) + ack, err := ibctesting.ParseAckFromEvents(result.Events) suite.Require().NoError(err) // Ensure that chainB has an ack. - storedAck, found := suite.chainB.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(suite.chainB.GetContext(), pathAtoB.EndpointB.ChannelConfig.PortID, pathAtoB.EndpointB.ChannelID, packet.GetSequence()) + storedAck, found := suite.chainB.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(suite.chainB.GetContext(), pathAtoB.EndpointB.ChannelConfig.PortID, pathAtoB.EndpointB.ChannelID, packetFromBToC.GetSequence()) suite.Require().True(found, "chainB does not have an ack") // And that this ack is of the type we expect (Error due to time out) - ack := internaltypes.NewForwardTimeoutAcknowledgement(packet) - ackbytes := channeltypes.CommitAcknowledgement(ack.Acknowledgement()) - suite.Require().Equal(ackbytes, storedAck) - - forwardingPacketData := types.NewForwardingPacketData("", forwarding.Hops...) - data = types.NewFungibleTokenPacketDataV2( - []types.Token{ - { - Denom: types.NewDenom(sdk.DefaultBondDenom), - Amount: "100", - }, - }, - sender.GetAddress().String(), - receiver.GetAddress().String(), - "", forwardingPacketData, - ) + expectedAck := internaltypes.NewForwardTimeoutAcknowledgement(packetFromBToC) + expectedAckBytes := channeltypes.CommitAcknowledgement(expectedAck.Acknowledgement()) + suite.Require().Equal(expectedAckBytes, storedAck) + suite.Require().Equal(expectedAck.Acknowledgement(), ack) - packet = channeltypes.NewPacket( - data.GetBytes(), - 1, - pathAtoB.EndpointA.ChannelConfig.PortID, - pathAtoB.EndpointA.ChannelID, - pathAtoB.EndpointB.ChannelConfig.PortID, - pathAtoB.EndpointB.ChannelID, - packet.TimeoutHeight, - packet.TimeoutTimestamp) - - // Send the ack to chain A. - err = suite.chainA.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainA.GetContext(), packet, data, ack) + err = pathAtoB.EndpointA.UpdateClient() + suite.Require().NoError(err) + err = pathAtoB.EndpointA.AcknowledgePacket(packetFromAToB, ack) suite.Require().NoError(err) // Finally, check that A,B, and C escrow accounts do not have fund. diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index b544ee0cf5d..a752a6f2530 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -92,24 +92,15 @@ func (suite *KeeperTestSuite) TestSendTransfer() { { "successful transfer of native token with ics20-1", func() { - coins = sdk.NewCoins(coins[0]) + denom := types.NewDenom(ibctesting.TestCoin.Denom) + coins = sdk.NewCoins(sdk.NewCoin(denom.IBCDenom(), ibctesting.TestCoin.Amount)) // Set version to isc20-1. path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.Version = types.V1 }) - }, - nil, - }, - { - "successful transfer with empty forwarding hops and ics20-1", - func() { - coins = sdk.NewCoins(coins[0]) - // Set version to isc20-1. - path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { - channel.Version = types.V1 - }) + expEscrowAmounts = []sdkmath.Int{defaultAmount} }, nil, }, @@ -463,7 +454,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket_ReceiverIsNotSource() { denoms = append(denoms, types.NewDenom(token.Denom.Base, types.NewHop(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID))) } - err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket(suite.chainB.GetContext(), packet, packetData) + _, err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainB.GetContext(), + packetData, + packet.SourcePort, + packet.SourceChannel, + packet.DestinationPort, + packet.DestinationChannel, + ) if tc.expError == nil { suite.Require().NoError(err) @@ -589,7 +587,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket_ReceiverIsSource() { tc.malleate() packet := channeltypes.NewPacket(packetData.GetBytes(), uint64(1), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, clienttypes.NewHeight(1, 100), 0) - err = suite.chainA.GetSimApp().TransferKeeper.OnRecvPacket(suite.chainA.GetContext(), packet, packetData) + _, err = suite.chainA.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainA.GetContext(), + packetData, + packet.SourcePort, + packet.SourceChannel, + packet.DestinationPort, + packet.DestinationChannel, + ) if tc.expError == nil { suite.Require().NoError(err) @@ -699,7 +704,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacketSetsTotalEscrowAmountForSourceIBCT suite.Require().Equal(defaultAmount, totalEscrowChainB.Amount) // execute onRecvPacket, when chaninB receives the source token the escrow amount should decrease - err := suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket(suite.chainB.GetContext(), packet, data) + _, err := suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainB.GetContext(), + data, + packet.SourcePort, + packet.SourceChannel, + packet.DestinationPort, + packet.DestinationChannel, + ) suite.Require().NoError(err) // check total amount in escrow of sent token on receiving chain @@ -800,7 +812,7 @@ func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() { packet := channeltypes.NewPacket(data.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) preAcknowledgementBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), denom.IBCDenom()) - err := suite.chainA.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainA.GetContext(), packet, data, tc.ack) + err := suite.chainA.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainA.GetContext(), packet.SourcePort, packet.SourceChannel, data, tc.ack) // check total amount in escrow of sent token denom on sending chain totalEscrow := suite.chainA.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainA.GetContext(), denom.IBCDenom()) @@ -906,7 +918,7 @@ func (suite *KeeperTestSuite) TestOnAcknowledgementPacketSetsTotalEscrowAmountFo totalEscrowChainB := suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) suite.Require().Equal(defaultAmount, totalEscrowChainB.Amount) - err := suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainB.GetContext(), packet, data, ack) + err := suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, data, ack) suite.Require().NoError(err) // check total amount in escrow of sent token on sending chain @@ -1034,7 +1046,7 @@ func (suite *KeeperTestSuite) TestOnTimeoutPacket() { packet := channeltypes.NewPacket(data.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) preTimeoutBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), denom.IBCDenom()) - err := suite.chainA.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainA.GetContext(), packet, data) + err := suite.chainA.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainA.GetContext(), packet.SourcePort, packet.SourceChannel, data) postTimeoutBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), denom.IBCDenom()) deltaAmount := postTimeoutBalance.Amount.Sub(preTimeoutBalance.Amount) @@ -1130,7 +1142,7 @@ func (suite *KeeperTestSuite) TestOnTimeoutPacketSetsTotalEscrowAmountForSourceI totalEscrowChainB := suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) suite.Require().Equal(defaultAmount, totalEscrowChainB.Amount) - err := suite.chainB.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainB.GetContext(), packet, data) + err := suite.chainB.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, data) suite.Require().NoError(err) // check total amount in escrow of sent token on sending chain diff --git a/modules/apps/transfer/v2/ibc_module.go b/modules/apps/transfer/v2/ibc_module.go index 84b25e259db..800e7e07e59 100644 --- a/modules/apps/transfer/v2/ibc_module.go +++ b/modules/apps/transfer/v2/ibc_module.go @@ -8,8 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/v9/modules/apps/transfer/keeper" transfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" - "github.com/cosmos/ibc-go/v9/modules/apps/transfer/v2/keeper" channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types" "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types" "github.com/cosmos/ibc-go/v9/modules/core/api" @@ -19,33 +19,42 @@ import ( var _ api.IBCModule = (*IBCModule)(nil) // NewIBCModule creates a new IBCModule given the keeper -func NewIBCModule(k *keeper.Keeper) *IBCModule { +func NewIBCModule(k keeper.Keeper) *IBCModule { return &IBCModule{ keeper: k, } } type IBCModule struct { - keeper *keeper.Keeper + keeper keeper.Keeper } func (im *IBCModule) OnSendPacket(goCtx context.Context, sourceChannel string, destinationChannel string, sequence uint64, payload types.Payload, signer sdk.AccAddress) error { - ctx := sdk.UnwrapSDKContext(goCtx) + data, err := transfertypes.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding) + if err != nil { + return err + } - if !im.keeper.GetParams(ctx).SendEnabled { - return transfertypes.ErrSendDisabled + sender, err := sdk.AccAddressFromBech32(data.Sender) + if err != nil { + return err } - if im.keeper.IsBlockedAddr(signer) { - return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to send funds", signer) + if !signer.Equals(sender) { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "sender %s is different from signer %s", sender, signer) } - data, err := transfertypes.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding) - if err != nil { + if err := im.keeper.SendTransfer(goCtx, payload.SourcePort, sourceChannel, data.Tokens, signer); err != nil { return err } - return im.keeper.OnSendPacket(ctx, sourceChannel, payload, data, signer) + // TODO: events + // events.EmitTransferEvent(ctx, sender.String(), receiver, tokens, memo, hops) + + // TODO: telemetry + // telemetry.ReportTransfer(sourcePort, sourceChannel, destinationPort, destinationChannel, tokens) + + return nil } func (im *IBCModule) OnRecvPacket(ctx context.Context, sourceChannel string, destinationChannel string, sequence uint64, payload types.Payload, relayer sdk.AccAddress) types.RecvPacketResult { @@ -77,7 +86,14 @@ func (im *IBCModule) OnRecvPacket(ctx context.Context, sourceChannel string, des } } - if ackErr = im.keeper.OnRecvPacket(ctx, sourceChannel, destinationChannel, payload, data); ackErr != nil { + if _, ackErr = im.keeper.OnRecvPacket( + ctx, + data, + payload.SourcePort, + sourceChannel, + payload.DestinationPort, + destinationChannel, + ); ackErr != nil { ack = channeltypes.NewErrorAcknowledgement(ackErr) im.keeper.Logger.Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), sequence)) return types.RecvPacketResult{ @@ -88,11 +104,26 @@ func (im *IBCModule) OnRecvPacket(ctx context.Context, sourceChannel string, des im.keeper.Logger.Info("successfully handled ICS-20 packet", "sequence", sequence) + // TODO: telemetry + // telemetry.ReportOnRecvPacket(packet, data.Tokens) + if data.HasForwarding() { - // NOTE: acknowledgement will be written asynchronously + // we are now sending from the forward escrow address to the final receiver address. + ack = channeltypes.NewErrorAcknowledgement(fmt.Errorf("forwarding not yet supported")) return types.RecvPacketResult{ - Status: types.PacketStatus_Async, + Status: types.PacketStatus_Failure, + Acknowledgement: ack.Acknowledgement(), } + // TODO: handle forwarding + // TODO: inside this version of the function, we should fetch the packet that was stored in IBC core in order to set it for forwarding. + // if err := k.forwardPacket(ctx, data, packet, receivedCoins); err != nil { + // return err + // } + + // NOTE: acknowledgement will be written asynchronously + // return types.RecvPacketResult{ + // Status: types.PacketStatus_Async, + // } } // NOTE: acknowledgement will be written synchronously during IBC handler execution. @@ -110,6 +141,8 @@ func (im *IBCModule) OnTimeoutPacket(ctx context.Context, sourceChannel string, return err } + // TODO: handle forwarding + return im.keeper.EmitOnTimeoutEvent(ctx, data) } @@ -128,5 +161,7 @@ func (im *IBCModule) OnAcknowledgementPacket(ctx context.Context, sourceChannel return err } + // TODO: handle forwarding + return im.keeper.EmitOnAcknowledgementPacketEvent(ctx, data, ack) } diff --git a/modules/apps/transfer/v2/keeper/keeper.go b/modules/apps/transfer/v2/keeper/keeper.go deleted file mode 100644 index cb2d271c6f2..00000000000 --- a/modules/apps/transfer/v2/keeper/keeper.go +++ /dev/null @@ -1,253 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - - transferkeeper "github.com/cosmos/ibc-go/v9/modules/apps/transfer/keeper" - "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types" - channelkeeperv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/keeper" - channeltypesv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types" - ibcerrors "github.com/cosmos/ibc-go/v9/modules/core/errors" -) - -type Keeper struct { - transferkeeper.Keeper - channelKeeperV2 *channelkeeperv2.Keeper -} - -func NewKeeper(transferKeeper transferkeeper.Keeper, channelKeeperV2 *channelkeeperv2.Keeper) *Keeper { - return &Keeper{ - Keeper: transferKeeper, - channelKeeperV2: channelKeeperV2, - } -} - -func (k *Keeper) OnSendPacket(ctx context.Context, sourceChannel string, payload channeltypesv2.Payload, data types.FungibleTokenPacketDataV2, sender sdk.AccAddress) error { - for _, token := range data.Tokens { - coin, err := token.ToCoin() - if err != nil { - return err - } - - if coin.Amount.Equal(types.UnboundedSpendLimit()) { - coin.Amount = k.BankKeeper.SpendableCoin(ctx, sender, coin.Denom).Amount - if coin.Amount.IsZero() { - return errorsmod.Wrapf(types.ErrInvalidAmount, "empty spendable balance for %s", coin.Denom) - } - } - - // NOTE: SendTransfer simply sends the denomination as it exists on its own - // chain inside the packet data. The receiving chain will perform denom - // prefixing as necessary. - - // if the denom is prefixed by the port and channel on which we are sending - // the token, then we must be returning the token back to the chain they originated from - if token.Denom.HasPrefix(payload.SourcePort, sourceChannel) { - // transfer the coins to the module account and burn them - if err := k.BankKeeper.SendCoinsFromAccountToModule( - ctx, sender, types.ModuleName, sdk.NewCoins(coin), - ); err != nil { - return err - } - - if err := k.BankKeeper.BurnCoins( - ctx, k.AuthKeeper.GetModuleAddress(types.ModuleName), sdk.NewCoins(coin), - ); err != nil { - // NOTE: should not happen as the module account was - // retrieved on the step above and it has enough balance - // to burn. - panic(fmt.Errorf("cannot burn coins after a successful send to a module account: %v", err)) - } - } else { - // obtain the escrow address for the source channel end - escrowAddress := types.GetEscrowAddress(payload.SourcePort, sourceChannel) - if err := k.EscrowCoin(ctx, sender, escrowAddress, coin); err != nil { - return err - } - } - } - - // TODO: events - // events.EmitTransferEvent(ctx, sender.String(), receiver, tokens, memo, hops) - - // TODO: telemetry - // telemetry.ReportTransfer(sourcePort, sourceChannel, destinationPort, destinationChannel, tokens) - - return nil -} - -func (k *Keeper) OnRecvPacket(ctx context.Context, sourceChannel, destChannel string, payload channeltypesv2.Payload, data types.FungibleTokenPacketDataV2) error { - // validate packet data upon receiving - if err := data.ValidateBasic(); err != nil { - return errorsmod.Wrapf(err, "error validating ICS-20 transfer packet data") - } - - if !k.GetParams(ctx).ReceiveEnabled { - return types.ErrReceiveDisabled - } - - receiver, err := sdk.AccAddressFromBech32(data.Receiver) - if err != nil { - return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "failed to decode receiver address %s: %v", data.Receiver, err) - } - - if k.IsBlockedAddr(receiver) { - return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to receive funds", receiver) - } - - receivedCoins := make(sdk.Coins, 0, len(data.Tokens)) - for _, token := range data.Tokens { - // parse the transfer amount - transferAmount, ok := sdkmath.NewIntFromString(token.Amount) - if !ok { - return errorsmod.Wrapf(types.ErrInvalidAmount, "unable to parse transfer amount: %s", token.Amount) - } - - // This is the prefix that would have been prefixed to the denomination - // on sender chain IF and only if the token originally came from the - // receiving chain. - // - // NOTE: We use SourcePort and SourceChannel here, because the counterparty - // chain would have prefixed with DestPort and DestChannel when originally - // receiving this token. - if token.Denom.HasPrefix(payload.SourcePort, sourceChannel) { - // sender chain is not the source, unescrow tokens - - // remove prefix added by sender chain - token.Denom.Trace = token.Denom.Trace[1:] - - coin := sdk.NewCoin(token.Denom.IBCDenom(), transferAmount) - - escrowAddress := types.GetEscrowAddress(payload.DestinationPort, destChannel) - if err := k.UnescrowCoin(ctx, escrowAddress, receiver, coin); err != nil { - return err - } - - // Appending token. The new denom has been computed - receivedCoins = append(receivedCoins, coin) - } else { - // sender chain is the source, mint vouchers - - // since SendPacket did not prefix the denomination, we must add the destination port and channel to the trace - trace := []types.Hop{types.NewHop(payload.DestinationPort, destChannel)} - token.Denom.Trace = append(trace, token.Denom.Trace...) - - if !k.HasDenom(ctx, token.Denom.Hash()) { - k.SetDenom(ctx, token.Denom) - } - - voucherDenom := token.Denom.IBCDenom() - if !k.BankKeeper.HasDenomMetaData(ctx, voucherDenom) { - k.SetDenomMetadata(ctx, token.Denom) - } - - if err := k.EmitDenomEvent(ctx, token); err != nil { - return err - } - - voucher := sdk.NewCoin(voucherDenom, transferAmount) - - // mint new tokens if the source of the transfer is the same chain - if err := k.BankKeeper.MintCoins( - ctx, types.ModuleName, sdk.NewCoins(voucher), - ); err != nil { - return errorsmod.Wrap(err, "failed to mint IBC tokens") - } - - // send to receiver - moduleAddr := k.AuthKeeper.GetModuleAddress(types.ModuleName) - if err := k.BankKeeper.SendCoins( - ctx, moduleAddr, receiver, sdk.NewCoins(voucher), - ); err != nil { - return errorsmod.Wrapf(err, "failed to send coins to receiver %s", receiver.String()) - } - - receivedCoins = append(receivedCoins, voucher) - } - } - - _ = receivedCoins // TODO: remove this line when forwarding is implemented - // TODO: forwarding - // if data.HasForwarding() { - // // we are now sending from the forward escrow address to the final receiver address. - // TODO: inside this version of the function, we should fetch the packet that was stored in IBC core in order to set it for forwarding. - // if err := k.forwardPacket(ctx, data, packet, receivedCoins); err != nil { - // return err - // } - // } - - // TODO: telemetry - // telemetry.ReportOnRecvPacket(packet, data.Tokens) - - // The ibc_module.go module will return the proper ack. - return nil -} - -func (k *Keeper) OnAcknowledgementPacket(ctx context.Context, sourcePort, sourceChannel string, data types.FungibleTokenPacketDataV2, ack channeltypes.Acknowledgement) error { - switch ack.Response.(type) { - case *channeltypes.Acknowledgement_Result: - // the acknowledgement succeeded on the receiving chain so nothing - // needs to be executed and no error needs to be returned - return nil - case *channeltypes.Acknowledgement_Error: - // We refund the tokens from the escrow address to the sender - return k.refundPacketTokens(ctx, sourcePort, sourceChannel, data) - default: - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected one of [%T, %T], got %T", channeltypes.Acknowledgement_Result{}, channeltypes.Acknowledgement_Error{}, ack.Response) - } -} - -func (k *Keeper) OnTimeoutPacket(ctx context.Context, sourcePort, sourceChannel string, data types.FungibleTokenPacketDataV2) error { - return k.refundPacketTokens(ctx, sourcePort, sourceChannel, data) -} - -func (k Keeper) refundPacketTokens(ctx context.Context, sourcePort, sourceChannel string, data types.FungibleTokenPacketDataV2) error { - // NOTE: packet data type already checked in handler.go - - sender, err := sdk.AccAddressFromBech32(data.Sender) - if err != nil { - return err - } - if k.IsBlockedAddr(sender) { - return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to receive funds", sender) - } - - // escrow address for unescrowing tokens back to sender - escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) - - moduleAccountAddr := k.AuthKeeper.GetModuleAddress(types.ModuleName) - for _, token := range data.Tokens { - coin, err := token.ToCoin() - if err != nil { - return err - } - - // if the token we must refund is prefixed by the source port and channel - // then the tokens were burnt when the packet was sent and we must mint new tokens - if token.Denom.HasPrefix(sourcePort, sourceChannel) { - // mint vouchers back to sender - if err := k.BankKeeper.MintCoins( - ctx, types.ModuleName, sdk.NewCoins(coin), - ); err != nil { - return err - } - - if err := k.BankKeeper.SendCoins(ctx, moduleAccountAddr, sender, sdk.NewCoins(coin)); err != nil { - panic(fmt.Errorf("unable to send coins from module to account despite previously minting coins to module account: %v", err)) - } - } else { - if err := k.UnescrowCoin(ctx, escrowAddress, sender, coin); err != nil { - return err - } - } - } - - return nil -} diff --git a/modules/apps/transfer/v2/keeper/keeper_test.go b/modules/apps/transfer/v2/keeper/keeper_test.go deleted file mode 100644 index 69836b14120..00000000000 --- a/modules/apps/transfer/v2/keeper/keeper_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package keeper_test - -import ( - "fmt" - "testing" - - testifysuite "github.com/stretchr/testify/suite" - - sdkmath "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - - ibctesting "github.com/cosmos/ibc-go/v9/testing" -) - -type KeeperTestSuite struct { - testifysuite.Suite - - coordinator *ibctesting.Coordinator - - // testing chains used for convenience and readability - chainA *ibctesting.TestChain - chainB *ibctesting.TestChain - chainC *ibctesting.TestChain -} - -func (suite *KeeperTestSuite) SetupTest() { - suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) - suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) -} - -type amountType int - -const ( - escrow amountType = iota - balance -) - -func (suite *KeeperTestSuite) assertAmountOnChain(chain *ibctesting.TestChain, balanceType amountType, amount sdkmath.Int, denom string) { - var total sdk.Coin - switch balanceType { - case escrow: - total = chain.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(chain.GetContext(), denom) - totalV2 := chain.GetSimApp().TransferKeeperV2.GetTotalEscrowForDenom(chain.GetContext(), denom) - suite.Require().Equal(total, totalV2, "escrow balance mismatch") - case balance: - total = chain.GetSimApp().BankKeeper.GetBalance(chain.GetContext(), chain.SenderAccounts[0].SenderAccount.GetAddress(), denom) - default: - suite.Fail("invalid amountType %s", balanceType) - } - suite.Require().Equal(amount, total.Amount, fmt.Sprintf("Chain %s: got balance of %s, wanted %s", chain.Name(), total.Amount.String(), amount.String())) -} - -func TestKeeperTestSuite(t *testing.T) { - testifysuite.Run(t, new(KeeperTestSuite)) -} diff --git a/modules/apps/transfer/v2/keeper/msg_server_test.go b/modules/apps/transfer/v2/keeper/msg_server_test.go deleted file mode 100644 index b4ab8e61016..00000000000 --- a/modules/apps/transfer/v2/keeper/msg_server_test.go +++ /dev/null @@ -1,750 +0,0 @@ -package keeper_test - -import ( - "bytes" - "encoding/json" - "time" - - "github.com/cosmos/solidity-ibc-eureka/abigen/ics20lib" - - sdkmath "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - - transfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types" - channeltypesv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types" - commitmenttypes "github.com/cosmos/ibc-go/v9/modules/core/23-commitment/types" - ibctesting "github.com/cosmos/ibc-go/v9/testing" - mockv2 "github.com/cosmos/ibc-go/v9/testing/mock/v2" -) - -// TestMsgSendPacketTransfer tests the MsgSendPacket rpc handler for the transfer v2 application. -func (suite *KeeperTestSuite) TestMsgSendPacketTransfer() { - var ( - payload channeltypesv2.Payload - path *ibctesting.Path - expEscrowAmounts []transfertypes.Token // total amounts in escrow for each token - sender ibctesting.SenderAccount - ) - - testCases := []struct { - name string - malleate func() - expError error - }{ - { - "success: v2 payload", - func() {}, - nil, - }, - { - "success: v1 payload", - func() { - ftpd := transfertypes.NewFungibleTokenPacketData(sdk.DefaultBondDenom, ibctesting.DefaultCoinAmount.String(), suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "") - bz, err := json.Marshal(ftpd) - suite.Require().NoError(err) - payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingJSON, bz) - }, - nil, - }, - { - "success: v1 ABI encoded payload", - func() { - bz, err := ics20lib.EncodeFungibleTokenPacketData(ics20lib.ICS20LibFungibleTokenPacketData{ - Denom: sdk.DefaultBondDenom, - Amount: ibctesting.DefaultCoinAmount.BigInt(), - Sender: suite.chainA.SenderAccount.GetAddress().String(), - Receiver: suite.chainB.SenderAccount.GetAddress().String(), - Memo: "", - }) - suite.Require().NoError(err) - payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingABI, bz) - }, - nil, - }, - // TODO: Update - // { - // "successful transfer of entire spendable balance with vesting account", - // func() { - // // create vesting account - // vestingAccPrivKey := secp256k1.GenPrivKey() - // vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address()) - // - // vestingCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, ibctesting.DefaultCoinAmount)) - // _, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount( - // suite.chainA.SenderAccount.GetAddress(), - // vestingAccAddress, - // vestingCoins, - // suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(), - // false, - // )) - // suite.Require().NoError(err) - // - // // transfer some spendable coins to vesting account - // spendableAmount := sdkmath.NewInt(42) - // transferCoins := sdk.NewCoins(sdk.NewCoin(vestingCoins[0].Denom, spendableAmount)) - // _, err = suite.chainA.SendMsgs(banktypes.NewMsgSend(suite.chainA.SenderAccount.GetAddress(), vestingAccAddress, transferCoins)) - // suite.Require().NoError(err) - // - // // just to prove that the vesting account has a balance (but only spendableAmount is spendable) - // vestingAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), vestingAccAddress, vestingCoins[0].Denom) - // suite.Require().Equal(vestingCoins[0].Amount.Uint64()+spendableAmount.Uint64(), vestingAccBalance.Amount.Uint64()) - // vestinSpendableBalance := suite.chainA.GetSimApp().BankKeeper.SpendableCoins(suite.chainA.GetContext(), vestingAccAddress) - // suite.Require().Equal(spendableAmount.Uint64(), vestinSpendableBalance.AmountOf(vestingCoins[0].Denom).Uint64()) - // - // bz, err := ics20lib.EncodeFungibleTokenPacketData(ics20lib.ICS20LibFungibleTokenPacketData{ - // Denom: sdk.DefaultBondDenom, - // Amount: transfertypes.UnboundedSpendLimit().BigInt(), - // Sender: vestingAccAddress.String(), - // Receiver: suite.chainB.SenderAccount.GetAddress().String(), - // Memo: "", - // }) - // suite.Require().NoError(err) - // payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingABI, bz) - // - // sender = suite.chainA.GetSenderAccount(vestingAccPrivKey) - // - // expEscrowAmounts = []transfertypes.Token{ - // { - // Denom: transfertypes.NewDenom(sdk.DefaultBondDenom), - // Amount: spendableAmount.String(), // The only spendable amount - // }, - // } - // }, - // nil, - // }, - // { - // "failure: no spendable coins for vesting account", - // func() { - // // create vesting account - // vestingAccPrivKey := secp256k1.GenPrivKey() - // vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address()) - // - // vestingCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, ibctesting.DefaultCoinAmount)) - // _, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount( - // suite.chainA.SenderAccount.GetAddress(), - // vestingAccAddress, - // vestingCoins, - // suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(), - // false, - // )) - // suite.Require().NoError(err) - // - // // just to prove that the vesting account has a balance (but not spendable) - // vestingAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), vestingAccAddress, vestingCoins[0].Denom) - // suite.Require().Equal(vestingCoins[0].Amount.Uint64(), vestingAccBalance.Amount.Uint64()) - // vestinSpendableBalance := suite.chainA.GetSimApp().BankKeeper.SpendableCoins(suite.chainA.GetContext(), vestingAccAddress) - // suite.Require().Zero(vestinSpendableBalance.AmountOf(vestingCoins[0].Denom).Uint64()) - // - // // try to transfer the entire spendable balance (which is zero) - // bz, err := ics20lib.EncodeFungibleTokenPacketData(ics20lib.ICS20LibFungibleTokenPacketData{ - // Denom: sdk.DefaultBondDenom, - // Amount: transfertypes.UnboundedSpendLimit().BigInt(), - // Sender: vestingAccAddress.String(), - // Receiver: suite.chainB.SenderAccount.GetAddress().String(), - // Memo: "", - // }) - // suite.Require().NoError(err) - // payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingABI, bz) - // - // sender = suite.chainA.GetSenderAccount(vestingAccPrivKey) - // }, - // transfertypes.ErrInvalidAmount, - // }, - { - "failure: send transfers disabled", - func() { - suite.chainA.GetSimApp().TransferKeeperV2.SetParams(suite.chainA.GetContext(), - transfertypes.Params{ - SendEnabled: false, - }, - ) - }, - transfertypes.ErrSendDisabled, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - - path = ibctesting.NewPath(suite.chainA, suite.chainB) - path.SetupV2() - - tokens := []transfertypes.Token{ - { - Denom: transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - Trace: nil, - }, - Amount: ibctesting.DefaultCoinAmount.String(), - }, - } - expEscrowAmounts = tokens - sender = suite.chainA.SenderAccounts[0] - - ftpd := transfertypes.NewFungibleTokenPacketDataV2(tokens, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "", transfertypes.ForwardingPacketData{}) - bz := suite.chainA.Codec.MustMarshal(&ftpd) - - timestamp := suite.chainA.GetTimeoutTimestampSecs() - payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V2, transfertypes.EncodingProtobuf, bz) - - tc.malleate() - packet, err := path.EndpointA.MsgSendPacketWithSender(timestamp, payload, sender) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - suite.Require().NotEmpty(packet) - - // ensure every token sent is escrowed. - for i, t := range tokens { - escrowedAmount := suite.chainA.GetSimApp().TransferKeeperV2.GetTotalEscrowForDenom(suite.chainA.GetContext(), t.Denom.IBCDenom()) - expected, err := expEscrowAmounts[i].ToCoin() - suite.Require().NoError(err) - suite.Require().Equal(expected, escrowedAmount, "escrowed amount is not equal to expected amount") - } - } else { - ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError, "expected error %q but got %q", tc.expError, err) - suite.Require().Empty(packet) - } - }) - } -} - -// TestMsgRecvPacketTransfer tests the MsgRecvPacket rpc handler for the transfer v2 application. -func (suite *KeeperTestSuite) TestMsgRecvPacketTransfer() { - var ( - path *ibctesting.Path - packet channeltypesv2.Packet - expectedAck channeltypesv2.Acknowledgement - sendPayload channeltypesv2.Payload - ) - - testCases := []struct { - name string - malleateSend func() - malleate func() - expError error - }{ - { - "success: v2 payload", - func() {}, - func() {}, - nil, - }, - { - "success: v1 payload", - func() { - ftpd := transfertypes.NewFungibleTokenPacketData(sdk.DefaultBondDenom, ibctesting.DefaultCoinAmount.String(), suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "") - bz, err := json.Marshal(ftpd) - suite.Require().NoError(err) - sendPayload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingJSON, bz) - }, - func() {}, - nil, - }, - { - "success: v1 ABI encoded payload", - func() { - bz, err := ics20lib.EncodeFungibleTokenPacketData(ics20lib.ICS20LibFungibleTokenPacketData{ - Denom: sdk.DefaultBondDenom, - Amount: ibctesting.DefaultCoinAmount.BigInt(), - Sender: suite.chainA.SenderAccount.GetAddress().String(), - Receiver: suite.chainB.SenderAccount.GetAddress().String(), - Memo: "", - }) - suite.Require().NoError(err) - sendPayload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingABI, bz) - }, - func() {}, - nil, - }, - { - "failure: invalid destination client on received packet", - func() {}, - func() { - packet.DestinationClient = ibctesting.InvalidID - }, - clienttypes.ErrCounterpartyNotFound, - }, - { - "failure: counter party client does not match source client", - func() {}, - func() { - packet.SourceClient = ibctesting.InvalidID - }, - clienttypes.ErrInvalidCounterparty, - }, - { - "failure: receive is disabled", - func() {}, - func() { - expectedAck.AppAcknowledgements[0] = channeltypes.NewErrorAcknowledgement(transfertypes.ErrReceiveDisabled).Acknowledgement() - suite.chainB.GetSimApp().TransferKeeperV2.SetParams(suite.chainB.GetContext(), - transfertypes.Params{ - ReceiveEnabled: false, - }) - }, - nil, - }, - // TODO: async tests - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - - path = ibctesting.NewPath(suite.chainA, suite.chainB) - path.SetupV2() - - tokens := []transfertypes.Token{ - { - Denom: transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - Trace: nil, - }, - Amount: ibctesting.DefaultCoinAmount.String(), - }, - } - - ftpd := transfertypes.NewFungibleTokenPacketDataV2(tokens, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "", transfertypes.ForwardingPacketData{}) - bz := suite.chainA.Codec.MustMarshal(&ftpd) - - timestamp := suite.chainA.GetTimeoutTimestampSecs() - sendPayload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V2, transfertypes.EncodingProtobuf, bz) - tc.malleateSend() - var err error - packet, err = path.EndpointA.MsgSendPacket(timestamp, sendPayload) - suite.Require().NoError(err) - - // by default, we assume a successful acknowledgement will be written. - ackBytes := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() - expectedAck = channeltypesv2.Acknowledgement{AppAcknowledgements: [][]byte{ackBytes}} - tc.malleate() - - err = path.EndpointB.MsgRecvPacket(packet) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - - actualAckHash := suite.chainB.GetSimApp().IBCKeeper.ChannelKeeperV2.GetPacketAcknowledgement(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) - expectedHash := channeltypesv2.CommitAcknowledgement(expectedAck) - - suite.Require().Equal(expectedHash, actualAckHash) - - denom := transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - Trace: []transfertypes.Hop{ - transfertypes.NewHop(sendPayload.DestinationPort, packet.DestinationClient), - }, - } - - actualBalance := path.EndpointB.Chain.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), denom.IBCDenom()) - - var expectedBalance sdk.Coin - // on a successful ack we expect the full amount to be transferred - if bytes.Equal(expectedAck.AppAcknowledgements[0], ackBytes) { - expectedBalance = sdk.NewCoin(denom.IBCDenom(), ibctesting.DefaultCoinAmount) - } else { - // otherwise the tokens do not make it to the address. - expectedBalance = sdk.NewCoin(denom.IBCDenom(), sdkmath.NewInt(0)) - } - - suite.Require().Equal(expectedBalance.Amount, actualBalance.Amount) - - } else { - ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError, "expected error %q but got %q", tc.expError, err) - } - }) - } -} - -// TestMsgAckPacketTransfer tests the MsgAcknowledgePacket rpc handler for the transfer v2 application. -func (suite *KeeperTestSuite) TestMsgAckPacketTransfer() { - var ( - path *ibctesting.Path - packet channeltypesv2.Packet - expectedAck channeltypesv2.Acknowledgement - ) - - testCases := []struct { - name string - malleate func() - expError error - causeFailureOnRecv bool - }{ - { - "success", - func() {}, - nil, - false, - }, - { - "failure: proof verification failure", - func() { - expectedAck.AppAcknowledgements[0] = mockv2.MockFailRecvPacketResult.Acknowledgement - }, - commitmenttypes.ErrInvalidProof, - false, - }, - { - "failure: escrowed tokens are refunded", - func() { - expectedAck.AppAcknowledgements[0] = channeltypes.NewErrorAcknowledgement(transfertypes.ErrReceiveDisabled).Acknowledgement() - }, - nil, - true, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - - path = ibctesting.NewPath(suite.chainA, suite.chainB) - path.SetupV2() - - tokens := []transfertypes.Token{ - { - Denom: transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - Trace: nil, - }, - Amount: ibctesting.DefaultCoinAmount.String(), - }, - } - - ftpd := transfertypes.NewFungibleTokenPacketDataV2(tokens, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "", transfertypes.ForwardingPacketData{}) - bz := suite.chainA.Codec.MustMarshal(&ftpd) - - timestamp := suite.chainA.GetTimeoutTimestampSecs() - payload := channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V2, transfertypes.EncodingProtobuf, bz) - - var err error - packet, err = path.EndpointA.MsgSendPacket(timestamp, payload) - suite.Require().NoError(err) - - if tc.causeFailureOnRecv { - // ensure that the recv packet fails at the application level, but succeeds at the IBC handler level - // this will ensure that a failed ack will be written to state. - suite.chainB.GetSimApp().TransferKeeperV2.SetParams(suite.chainB.GetContext(), - transfertypes.Params{ - ReceiveEnabled: false, - }) - } - - err = path.EndpointB.MsgRecvPacket(packet) - suite.Require().NoError(err) - - ackBytes := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() - expectedAck = channeltypesv2.Acknowledgement{AppAcknowledgements: [][]byte{ackBytes}} - tc.malleate() - - err = path.EndpointA.MsgAcknowledgePacket(packet, expectedAck) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - - if bytes.Equal(expectedAck.AppAcknowledgements[0], ackBytes) { - // tokens remain escrowed - for _, t := range tokens { - escrowedAmount := suite.chainA.GetSimApp().TransferKeeperV2.GetTotalEscrowForDenom(suite.chainA.GetContext(), t.Denom.IBCDenom()) - expected, err := t.ToCoin() - suite.Require().NoError(err) - suite.Require().Equal(expected, escrowedAmount, "escrowed amount is not equal to expected amount") - } - } else { - // tokens have been unescrowed - for _, t := range tokens { - escrowedAmount := suite.chainA.GetSimApp().TransferKeeperV2.GetTotalEscrowForDenom(suite.chainA.GetContext(), t.Denom.IBCDenom()) - suite.Require().Equal(sdk.NewCoin(t.Denom.IBCDenom(), sdkmath.NewInt(0)), escrowedAmount, "escrowed amount is not equal to expected amount") - } - } - } else { - ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError, "expected error %q but got %q", tc.expError, err) - } - }) - } -} - -// TestMsgTimeoutPacketTransfer tests the MsgTimeoutPacket rpc handler for the transfer v2 application. -func (suite *KeeperTestSuite) TestMsgTimeoutPacketTransfer() { - var ( - path *ibctesting.Path - packet channeltypesv2.Packet - timeoutTimestamp uint64 - ) - - testCases := []struct { - name string - malleate func() - timeoutPacket bool - expError error - }{ - { - "success", - func() {}, - true, - nil, - }, - { - "failure: packet has not timed out", - func() {}, - false, - channeltypes.ErrTimeoutNotReached, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - - path = ibctesting.NewPath(suite.chainA, suite.chainB) - path.SetupV2() - - tokens := []transfertypes.Token{ - { - Denom: transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - Trace: nil, - }, - Amount: ibctesting.DefaultCoinAmount.String(), - }, - } - - ftpd := transfertypes.NewFungibleTokenPacketDataV2(tokens, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "", transfertypes.ForwardingPacketData{}) - bz := suite.chainA.Codec.MustMarshal(&ftpd) - - timeoutTimestamp = uint64(suite.chainA.GetContext().BlockTime().Unix()) + uint64(time.Hour.Seconds()) - payload := channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V2, transfertypes.EncodingProtobuf, bz) - - var err error - packet, err = path.EndpointA.MsgSendPacket(timeoutTimestamp, payload) - suite.Require().NoError(err) - - if tc.timeoutPacket { - suite.coordinator.IncrementTimeBy(time.Hour * 2) - } - - // ensure that chainA has an update to date client of chain B. - suite.Require().NoError(path.EndpointA.UpdateClient()) - - tc.malleate() - - err = path.EndpointA.MsgTimeoutPacket(packet) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - - // ensure funds are un-escrowed - for _, t := range tokens { - escrowedAmount := suite.chainA.GetSimApp().TransferKeeperV2.GetTotalEscrowForDenom(suite.chainA.GetContext(), t.Denom.IBCDenom()) - suite.Require().Equal(sdk.NewCoin(t.Denom.IBCDenom(), sdkmath.NewInt(0)), escrowedAmount, "escrowed amount is not equal to expected amount") - } - - } else { - ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError, "expected error %q but got %q", tc.expError, err) - // tokens remain escrowed if there is a timeout failure - for _, t := range tokens { - escrowedAmount := suite.chainA.GetSimApp().TransferKeeperV2.GetTotalEscrowForDenom(suite.chainA.GetContext(), t.Denom.IBCDenom()) - expected, err := t.ToCoin() - suite.Require().NoError(err) - suite.Require().Equal(expected, escrowedAmount, "escrowed amount is not equal to expected amount") - } - } - }) - } -} - -func (suite *KeeperTestSuite) TestV2RetainsFungibility() { - suite.SetupTest() - - path := ibctesting.NewTransferPath(suite.chainA, suite.chainB) - path.Setup() - - pathv2 := ibctesting.NewPath(suite.chainB, suite.chainC) - pathv2.SetupV2() - - denomA := transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - } - - denomAtoB := transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - Trace: []transfertypes.Hop{ - transfertypes.NewHop(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), - }, - } - - denomBtoC := transfertypes.Denom{ - Base: sdk.DefaultBondDenom, - Trace: []transfertypes.Hop{ - transfertypes.NewHop(transfertypes.ModuleName, pathv2.EndpointB.ClientID), - transfertypes.NewHop(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), - }, - } - - ackBytes := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() - successfulAck := channeltypesv2.Acknowledgement{AppAcknowledgements: [][]byte{ackBytes}} - - originalAmount, ok := sdkmath.NewIntFromString(ibctesting.DefaultGenesisAccBalance) - suite.Require().True(ok) - - suite.Run("between A and B", func() { - var packet channeltypes.Packet - suite.Run("transfer packet", func() { - transferMsg := transfertypes.NewMsgTransfer( - path.EndpointA.ChannelConfig.PortID, - path.EndpointA.ChannelID, - sdk.NewCoins(sdk.NewCoin(denomA.IBCDenom(), ibctesting.TestCoin.Amount)), - suite.chainA.SenderAccount.GetAddress().String(), - suite.chainB.SenderAccount.GetAddress().String(), - clienttypes.ZeroHeight(), - suite.chainA.GetTimeoutTimestamp(), - "memo", - nil, - ) - - result, err := suite.chainA.SendMsgs(transferMsg) - suite.Require().NoError(err) // message committed - - remainingAmount := originalAmount.Sub(ibctesting.DefaultCoinAmount) - suite.assertAmountOnChain(suite.chainA, balance, remainingAmount, denomA.IBCDenom()) - - packet, err = ibctesting.ParsePacketFromEvents(result.Events) - suite.Require().NoError(err) - }) - - suite.Run("recv and ack packet", func() { - err := path.RelayPacket(packet) - suite.Require().NoError(err) - }) - }) - - suite.Run("between B and C", func() { - var packetV2 channeltypesv2.Packet - - suite.Run("send packet", func() { - tokens := []transfertypes.Token{ - { - Denom: denomAtoB, - Amount: ibctesting.DefaultCoinAmount.String(), - }, - } - - ftpd := transfertypes.NewFungibleTokenPacketDataV2(tokens, suite.chainB.SenderAccount.GetAddress().String(), suite.chainC.SenderAccount.GetAddress().String(), "", transfertypes.ForwardingPacketData{}) - bz := suite.chainB.Codec.MustMarshal(&ftpd) - - timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Unix()) + uint64(time.Hour.Seconds()) - payload := channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V2, transfertypes.EncodingProtobuf, bz) - - var err error - packetV2, err = pathv2.EndpointA.MsgSendPacket(timeoutTimestamp, payload) - suite.Require().NoError(err) - // the escrow account on chain B should have escrowed the tokens after sending from B to C - suite.assertAmountOnChain(suite.chainB, escrow, ibctesting.DefaultCoinAmount, denomAtoB.IBCDenom()) - }) - - suite.Run("recv packet", func() { - err := pathv2.EndpointB.MsgRecvPacket(packetV2) - suite.Require().NoError(err) - - // the receiving chain should have received the tokens - suite.assertAmountOnChain(suite.chainC, balance, ibctesting.DefaultCoinAmount, denomBtoC.IBCDenom()) - }) - - suite.Run("ack packet", func() { - err := pathv2.EndpointA.MsgAcknowledgePacket(packetV2, successfulAck) - suite.Require().NoError(err) - }) - }) - - suite.Run("between C and B", func() { - var packetV2 channeltypesv2.Packet - - suite.Run("send packet", func() { - // send from C to B - tokens := []transfertypes.Token{ - { - Denom: denomBtoC, - Amount: ibctesting.DefaultCoinAmount.String(), - }, - } - - ftpd := transfertypes.NewFungibleTokenPacketDataV2(tokens, suite.chainC.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "", transfertypes.ForwardingPacketData{}) - bz := suite.chainC.Codec.MustMarshal(&ftpd) - - timeoutTimestamp := uint64(suite.chainC.GetContext().BlockTime().Unix()) + uint64(time.Hour.Seconds()) - payload := channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V2, transfertypes.EncodingProtobuf, bz) - - var err error - packetV2, err = pathv2.EndpointB.MsgSendPacket(timeoutTimestamp, payload) - suite.Require().NoError(err) - - // tokens have been sent from chain C, and the balance is now empty. - suite.assertAmountOnChain(suite.chainC, balance, sdkmath.NewInt(0), denomBtoC.IBCDenom()) - }) - - suite.Run("recv packet", func() { - err := pathv2.EndpointA.MsgRecvPacket(packetV2) - suite.Require().NoError(err) - - // chain B should have received the tokens from chain C. - suite.assertAmountOnChain(suite.chainB, balance, ibctesting.DefaultCoinAmount, denomAtoB.IBCDenom()) - }) - - suite.Run("ack packet", func() { - err := pathv2.EndpointB.MsgAcknowledgePacket(packetV2, successfulAck) - suite.Require().NoError(err) - }) - }) - - suite.Run("between B and A", func() { - var packet channeltypes.Packet - - suite.Run("transfer packet", func() { - // send from B to A using MsgTransfer - transferMsg := transfertypes.NewMsgTransfer( - path.EndpointB.ChannelConfig.PortID, - path.EndpointB.ChannelID, - sdk.NewCoins(sdk.NewCoin(denomAtoB.IBCDenom(), ibctesting.TestCoin.Amount)), - suite.chainB.SenderAccount.GetAddress().String(), - suite.chainA.SenderAccount.GetAddress().String(), - clienttypes.ZeroHeight(), - suite.chainB.GetTimeoutTimestamp(), - "memo", - nil, - ) - - result, err := suite.chainB.SendMsgs(transferMsg) - suite.Require().NoError(err) // message committed - - suite.assertAmountOnChain(suite.chainB, balance, sdkmath.NewInt(0), denomAtoB.IBCDenom()) - - packet, err = ibctesting.ParsePacketFromEvents(result.Events) - suite.Require().NoError(err) - }) - suite.Run("recv and ack packet", func() { - // in order to recv in the other direction, we create a new path and recv - // on that with the endpoints reversed. - err := path.Reversed().RelayPacket(packet) - suite.Require().NoError(err) - - suite.assertAmountOnChain(suite.chainA, balance, originalAmount, denomA.IBCDenom()) - }) - }) -} diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go index f6b7978d09f..59176605eae 100644 --- a/modules/light-clients/08-wasm/testing/simapp/app.go +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -131,7 +131,6 @@ import ( ibctransferkeeper "github.com/cosmos/ibc-go/v9/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" transferv2 "github.com/cosmos/ibc-go/v9/modules/apps/transfer/v2" - ibctransferkeeperv2 "github.com/cosmos/ibc-go/v9/modules/apps/transfer/v2/keeper" ibc "github.com/cosmos/ibc-go/v9/modules/core" ibcclienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" ibcconnectiontypes "github.com/cosmos/ibc-go/v9/modules/core/03-connection/types" @@ -210,7 +209,6 @@ type SimApp struct { ICAHostKeeper icahostkeeper.Keeper EvidenceKeeper evidencekeeper.Keeper TransferKeeper ibctransferkeeper.Keeper - TransferKeeperV2 *ibctransferkeeperv2.Keeper WasmClientKeeper wasmkeeper.Keeper FeeGrantKeeper feegrantkeeper.Keeper GroupKeeper groupkeeper.Keeper @@ -692,8 +690,7 @@ func NewSimApp( ibcRouter.AddRoute(MockFeePort, feeWithMockModule) // register the transfer v2 module. - app.TransferKeeperV2 = ibctransferkeeperv2.NewKeeper(app.TransferKeeper, app.IBCKeeper.ChannelKeeperV2) - ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferv2.NewIBCModule(app.TransferKeeperV2)) + ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferv2.NewIBCModule(app.TransferKeeper)) // Seal the IBC Routers. app.IBCKeeper.SetRouter(ibcRouter) diff --git a/testing/endpoint.go b/testing/endpoint.go index 869f92a8ee8..7994df5b512 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -542,8 +542,8 @@ func (endpoint *Endpoint) AcknowledgePacketWithResult(packet channeltypes.Packet return endpoint.Chain.SendMsgs(ackMsg) } -// TimeoutPacket sends a MsgTimeout to the channel associated with the endpoint. -func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { +// TimeoutPacketWithResult sends a MsgTimeout to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutPacketWithResult(packet channeltypes.Packet) (*abci.ExecTxResult, error) { // get proof for timeout based on channel order var packetKey []byte @@ -553,7 +553,7 @@ func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { case channeltypes.UNORDERED: packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) default: - return fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) + return nil, fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) } counterparty := endpoint.Counterparty @@ -566,7 +566,13 @@ func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), ) - return endpoint.Chain.sendMsgs(timeoutMsg) + return endpoint.Chain.SendMsgs(timeoutMsg) +} + +// TimeoutPacket sends a MsgTimeout to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { + _, err := endpoint.TimeoutPacketWithResult(packet) + return err } // TimeoutOnClose sends a MsgTimeoutOnClose to the channel associated with the endpoint. diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 9b0a6f9e1e5..0f85e05e200 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -130,7 +130,6 @@ import ( ibctransferkeeper "github.com/cosmos/ibc-go/v9/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" transferv2 "github.com/cosmos/ibc-go/v9/modules/apps/transfer/v2" - ibctransferkeeperv2 "github.com/cosmos/ibc-go/v9/modules/apps/transfer/v2/keeper" ibc "github.com/cosmos/ibc-go/v9/modules/core" ibcclienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" ibcconnectiontypes "github.com/cosmos/ibc-go/v9/modules/core/03-connection/types" @@ -216,7 +215,6 @@ type SimApp struct { ICAControllerKeeper icacontrollerkeeper.Keeper ICAHostKeeper icahostkeeper.Keeper TransferKeeper ibctransferkeeper.Keeper - TransferKeeperV2 *ibctransferkeeperv2.Keeper // make IBC modules public for test purposes // these modules are never directly routed to by the IBC Router @@ -655,8 +653,7 @@ func NewSimApp( app.MockModuleV2B = mockV2B // register the transfer v2 module. - app.TransferKeeperV2 = ibctransferkeeperv2.NewKeeper(app.TransferKeeper, app.IBCKeeper.ChannelKeeperV2) - ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferv2.NewIBCModule(app.TransferKeeperV2)) + ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferv2.NewIBCModule(app.TransferKeeper)) // Seal the IBC Router app.IBCKeeper.SetRouter(ibcRouter)