Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

# Changelog

## [Unreleased]

* [\#8573](https://github.com/cosmos/ibc-go/pull/8573) Support custom address codecs in transfer.

## [v10.3.0](https://github.com/cosmos/ibc-go/releases/tag/v10.3.0) - 2025-06-06

### Features
Expand Down
16 changes: 16 additions & 0 deletions modules/apps/transfer/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"strings"

"cosmossdk.io/core/address"
corestore "cosmossdk.io/core/store"
"cosmossdk.io/log"
sdkmath "cosmossdk.io/math"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

cmtbytes "github.com/cometbft/cometbft/libs/bytes"
Expand All @@ -28,6 +30,7 @@ type Keeper struct {
storeService corestore.KVStoreService
cdc codec.BinaryCodec
legacySubspace types.ParamSubspace
addressCodec address.Codec

ics4Wrapper porttypes.ICS4Wrapper
channelKeeper types.ChannelKeeper
Expand Down Expand Up @@ -61,10 +64,13 @@ func NewKeeper(
panic(errors.New("authority must be non-empty"))
}

addressCodec := authcodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix())

return Keeper{
cdc: cdc,
storeService: storeService,
legacySubspace: legacySubspace,
addressCodec: addressCodec,
ics4Wrapper: ics4Wrapper,
channelKeeper: channelKeeper,
msgRouter: msgRouter,
Expand All @@ -91,6 +97,16 @@ func (k Keeper) GetAuthority() string {
return k.authority
}

// SetAddressCodec sets the address codec used by the keeper.
func (k *Keeper) SetAddressCodec(addressCodec address.Codec) {
k.addressCodec = addressCodec
}

// GetAddressCodec returns the address codec used by the keeper.
func (k *Keeper) GetAddressCodec() address.Codec {
return k.addressCodec
}

Comment on lines +100 to +109
Copy link
Member

Choose a reason for hiding this comment

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

Can you instantiate address codec in NewKeeper with a default SDK keeper? In case they forget to call SetAddressCodec?

I see you opted to use k.addressCodec != nil in line. Is there a reason for this?

Copy link
Author

Choose a reason for hiding this comment

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

this backport is to avoid API breaking change.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think what he meant was that inside the NewKeeper function, you should set up a default address codec, so that someone not changing their app.go to include the SetAddressCodec would not break.

Copy link
Author

Choose a reason for hiding this comment

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

ah, sounds good, changed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great! Thanks 🙏

// Logger returns a module-specific logger.
func (Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+exported.ModuleName+"-"+types.ModuleName)
Expand Down
10 changes: 5 additions & 5 deletions modules/apps/transfer/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.
return nil, types.ErrSendDisabled
}

sender, err := sdk.AccAddressFromBech32(msg.Sender)
sender, err := k.addressCodec.StringToBytes(msg.Sender)
if err != nil {
return nil, err
}
Expand All @@ -47,7 +47,7 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.
return nil, err
}

packetData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, sender.String(), msg.Receiver, msg.Memo)
packetData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, msg.Sender, msg.Receiver, msg.Memo)

if err := packetData.ValidateBasic(); err != nil {
return nil, errorsmod.Wrapf(err, "failed to validate %s packet data", types.V1)
Expand All @@ -60,7 +60,7 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.
var sequence uint64
if isIBCV1 {
// if a V1 channel exists for the source channel, then use IBC V1 protocol
sequence, err = k.transferV1Packet(ctx, msg.SourceChannel, token, msg.TimeoutHeight, msg.TimeoutTimestamp, packetData)
sequence, err = k.transferV1Packet(ctx, msg.SourceChannel, token, msg.TimeoutHeight, msg.TimeoutTimestamp, sender, packetData)
// telemetry for transfer occurs here, in IBC V2 this is done in the onSendPacket callback
telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, channel.Counterparty.PortId, channel.Counterparty.ChannelId, token)
} else {
Expand All @@ -77,8 +77,8 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.
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 {
func (k *Keeper) transferV1Packet(ctx sdk.Context, sourceChannel string, token types.Token, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, sender sdk.AccAddress, packetData types.FungibleTokenPacketData) (uint64, error) {
if err := k.SendTransfer(ctx, types.PortID, sourceChannel, token, sender); err != nil {
return 0, err
}

Expand Down
7 changes: 7 additions & 0 deletions modules/apps/transfer/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ func (suite *KeeperTestSuite) TestMsgTransfer() {
},
types.ErrSendDisabled,
},
{
"failure: zero amount",
func() {
msg.Token = sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)
},
types.ErrInvalidAmount,
},
{
"failure: invalid sender",
func() {
Expand Down
6 changes: 3 additions & 3 deletions modules/apps/transfer/keeper/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (k Keeper) OnRecvPacket(
return types.ErrReceiveDisabled
}

receiver, err := sdk.AccAddressFromBech32(data.Receiver)
receiver, err := k.addressCodec.StringToBytes(data.Receiver)
if err != nil {
return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "failed to decode receiver address: %s", data.Receiver)
}
Expand Down Expand Up @@ -195,7 +195,7 @@ func (k Keeper) OnRecvPacket(
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 errorsmod.Wrapf(err, "failed to send coins to receiver %s", data.Receiver)
}

}
Expand Down Expand Up @@ -253,7 +253,7 @@ func (k Keeper) refundPacketTokens(
) error {
// NOTE: packet data type already checked in handler.go

sender, err := sdk.AccAddressFromBech32(data.Sender)
sender, err := k.addressCodec.StringToBytes(data.Sender)
if err != nil {
return err
}
Expand Down
27 changes: 19 additions & 8 deletions modules/apps/transfer/keeper/relay_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"encoding/hex"
"errors"
"fmt"
"strings"
Expand Down Expand Up @@ -47,19 +48,19 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
expError error
}{
{
"successful transfer of native token",
"success: transfer of native token",
func() {},
nil,
},
{
"successful transfer of native token with memo",
"success: transfer of native token with memo",
func() {
memo = "memo" //nolint:goconst
},
nil,
},
{
"successful transfer of IBC token",
"success: transfer of IBC token",
func() {
// send IBC token back to chainB
denom := types.NewDenom(ibctesting.TestCoin.Denom, types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID))
Expand All @@ -70,7 +71,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
nil,
},
{
"successful transfer of IBC token with memo",
"success: transfer of IBC token with memo",
func() {
// send IBC token back to chainB
denom := types.NewDenom(ibctesting.TestCoin.Denom, types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID))
Expand All @@ -82,7 +83,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
nil,
},
{
"successful transfer of entire balance",
"success: transfer of entire balance",
func() {
coin = sdk.NewCoin(coin.Denom, types.UnboundedSpendLimit())
var ok bool
Expand All @@ -92,7 +93,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
nil,
},
{
"successful transfer of entire spendable balance with vesting account",
"success: transfer of entire spendable balance with vesting account",
func() {
// create vesting account
vestingAccPrivKey := secp256k1.GenPrivKey()
Expand Down Expand Up @@ -325,17 +326,27 @@ func (suite *KeeperTestSuite) TestOnRecvPacket_ReceiverIsNotSource() {
expError error
}{
{
"successful receive",
"success: receive",
func() {},
nil,
},
{
"successful receive with memo",
"success: receive with memo",
func() {
packetData.Memo = "memo"
},
nil,
},
{
"success: receive with hex receiver address",
func() {
suite.chainB.GetSimApp().TransferKeeper.SetAddressCodec(ibcmock.TestAddressCodec{})

receiver := sdk.MustAccAddressFromBech32(packetData.Receiver)
packetData.Receiver = hex.EncodeToString(receiver.Bytes())
},
nil,
},
{
"failure: mint zero coin",
func() {
Expand Down
10 changes: 4 additions & 6 deletions modules/apps/transfer/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ func NewMsgUpdateParams(signer string, params Params) *MsgUpdateParams {

// ValidateBasic implements sdk.Msg
func (msg MsgUpdateParams) ValidateBasic() error {
_, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err)
if strings.TrimSpace(msg.Signer) == "" {
return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "missing sender address")
}

return nil
Expand Down Expand Up @@ -96,9 +95,8 @@ func (msg MsgTransfer) ValidateBasic() error {
return errorsmod.Wrap(ibcerrors.ErrInvalidCoins, msg.Token.String())
}

_, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err)
if strings.TrimSpace(msg.Sender) == "" {
return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "missing sender address")
}
if strings.TrimSpace(msg.Receiver) == "" {
return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "missing recipient address")
Expand Down
1 change: 0 additions & 1 deletion modules/apps/transfer/types/msgs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ func TestMsgUpdateParamsValidateBasic(t *testing.T) {
expError error
}{
{"success: valid signer and valid params", types.NewMsgUpdateParams(ibctesting.TestAccAddress, types.DefaultParams()), nil},
{"failure: invalid signer with valid params", types.NewMsgUpdateParams(invalidAddress, types.DefaultParams()), ibcerrors.ErrInvalidAddress},
{"failure: empty signer with valid params", types.NewMsgUpdateParams(emptyAddr, types.DefaultParams()), ibcerrors.ErrInvalidAddress},
}

Expand Down
7 changes: 4 additions & 3 deletions modules/apps/transfer/v2/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ func (im *IBCModule) OnSendPacket(ctx sdk.Context, sourceChannel string, destina
return err
}

sender, err := sdk.AccAddressFromBech32(data.Sender)
addressCodec := im.keeper.GetAddressCodec()
sender, err := addressCodec.StringToBytes(data.Sender)
if err != nil {
return err
}

if !signer.Equals(sender) {
if !bytes.Equal(sender, signer) {
return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "sender %s is different from signer %s", sender, signer)
}

Expand All @@ -76,7 +77,7 @@ func (im *IBCModule) OnSendPacket(ctx sdk.Context, sourceChannel string, destina
return err
}

events.EmitTransferEvent(ctx, sender.String(), data.Receiver, data.Token, data.Memo)
events.EmitTransferEvent(ctx, data.Sender, data.Receiver, data.Token, data.Memo)

telemetry.ReportTransfer(payload.SourcePort, sourceChannel, payload.DestinationPort, destinationChannel, data.Token)

Expand Down
27 changes: 27 additions & 0 deletions testing/mock/address_codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package mock

import (
"errors"

sdk "github.com/cosmos/cosmos-sdk/types"
)

type TestAddressCodec struct{}

func (t TestAddressCodec) StringToBytes(text string) ([]byte, error) {
hexBytes, err := sdk.AccAddressFromHexUnsafe(text)
if err == nil {
return hexBytes, nil
}

bech32Bytes, err := sdk.AccAddressFromBech32(text)
if err == nil {
return bech32Bytes, nil
}

return nil, errors.New("invalid address format")
}

func (t TestAddressCodec) BytesToString(bz []byte) (string, error) {
return sdk.AccAddress(bz).String(), nil
}
Loading