Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(transfer): add forwarding info validation to token packet #6571

1 change: 1 addition & 0 deletions modules/apps/transfer/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ var (
ErrMaxTransferChannels = errorsmod.Register(ModuleName, 9, "max transfer channels")
ErrInvalidAuthorization = errorsmod.Register(ModuleName, 10, "invalid transfer authorization")
ErrInvalidMemo = errorsmod.Register(ModuleName, 11, "invalid memo")
ErrInvalidForwardingInfo = errorsmod.Register(ModuleName, 12, "invalid forwarding info")
)
39 changes: 39 additions & 0 deletions modules/apps/transfer/types/forwarding_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package types

import (
errorsmod "cosmossdk.io/errors"

host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
)

const MaximumNumberOfForwardingHops = 64

// NewForwardingInfo creates a new ForwardingInfo instance given a memo and a variable number of hops.
func NewForwardingInfo(memo string, hops ...*Hop) *ForwardingInfo {
return &ForwardingInfo{
Memo: memo,
Hops: hops,
}
}

// ValidateBasic performs a basic validation of the ForwardingInfo fields.
func (fi ForwardingInfo) ValidateBasic() error {
if len(fi.Hops) > MaximumNumberOfForwardingHops {
return errorsmod.Wrapf(ErrInvalidForwardingInfo, "number of hops in forwarding path cannot exceed %d", MaximumNumberOfForwardingHops)
}

for _, hop := range fi.Hops {
if err := host.PortIdentifierValidator(hop.PortId); err != nil {
return errorsmod.Wrapf(err, "invalid source port ID %s", hop.PortId)
}
if err := host.ChannelIdentifierValidator(hop.ChannelId); err != nil {
return errorsmod.Wrapf(err, "invalid source channel ID %s", hop.ChannelId)
}
}

if len(fi.Memo) > MaximumMemoLength {
return errorsmod.Wrapf(ErrInvalidMemo, "memo length cannot exceed %d", MaximumMemoLength)
}

return nil
}
151 changes: 151 additions & 0 deletions modules/apps/transfer/types/forwarding_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package types_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
)

var validHop = &types.Hop{
PortId: types.PortID,
ChannelId: ibctesting.FirstChannelID,
}

func TestForwardingInfo_Validate(t *testing.T) {
tests := []struct {
name string
forwardingInfo *types.ForwardingInfo
expError error
}{
{
"valid forwarding info with no hops",
types.NewForwardingInfo(""),
nil,
},
{
"valid forwarding info with hops",
types.NewForwardingInfo("", validHop),
nil,
},
{
"valid forwarding info with memo",
types.NewForwardingInfo(testMemo1, validHop, validHop),
nil,
},
{
"valid forwarding info with max hops",
types.NewForwardingInfo("", generateHops(types.MaximumNumberOfForwardingHops)...),
nil,
},
{
"valid forwarding info with max memo length",
types.NewForwardingInfo(ibctesting.GenerateString(types.MaximumMemoLength), validHop),
nil,
},
{
"invalid forwarding info with too many hops",
types.NewForwardingInfo("", generateHops(types.MaximumNumberOfForwardingHops+1)...),
types.ErrInvalidForwardingInfo,
},
{
"invalid forwarding info with too long memo",
types.NewForwardingInfo(ibctesting.GenerateString(types.MaximumMemoLength+1), validHop),
types.ErrInvalidMemo,
},
{
"invalid forwarding info with too short hop port ID",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: invalidShortPort,
ChannelId: ibctesting.FirstChannelID,
},
),
host.ErrInvalidID,
},
{
"invalid forwarding info with too long hop port ID",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: invalidLongPort,
ChannelId: ibctesting.FirstChannelID,
},
),
host.ErrInvalidID,
},
{
"invalid forwarding info with non-alpha hop port ID",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: invalidPort,
ChannelId: ibctesting.FirstChannelID,
},
),
host.ErrInvalidID,
},
{
"invalid forwarding info with too long hop channel ID",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: types.PortID,
ChannelId: invalidLongChannel,
},
),
host.ErrInvalidID,
},
{
"invalid forwarding info with too short hop channel ID",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: types.PortID,
ChannelId: invalidShortChannel,
},
),
host.ErrInvalidID,
},
{
"invalid forwarding info with non-alpha hop channel ID",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: types.PortID,
ChannelId: invalidChannel,
},
),
host.ErrInvalidID,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc := tc

err := tc.forwardingInfo.ValidateBasic()

expPass := tc.expError == nil
if expPass {
require.NoError(t, err)
} else {
require.ErrorIs(t, err, tc.expError)
}
})
}
}

func generateHops(n int) []*types.Hop {
hops := make([]*types.Hop, n)
for i := 0; i < n; i++ {
hops[i] = &types.Hop{
PortId: types.PortID,
ChannelId: ibctesting.FirstChannelID,
}
}
return hops
}
12 changes: 9 additions & 3 deletions modules/apps/transfer/types/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,15 @@ func (ftpd FungibleTokenPacketDataV2) ValidateBasic() error {
return errorsmod.Wrapf(ErrInvalidMemo, "memo must not exceed %d bytes", MaximumMemoLength)
}

// We cannot have non-empty memo and non-empty forwarding path hops at the same time.
if ftpd.ForwardingPath != nil && len(ftpd.ForwardingPath.Hops) > 0 && ftpd.Memo != "" {
return errorsmod.Wrapf(ErrInvalidMemo, "memo must be empty if forwarding path hops is not empty: %s, %s", ftpd.Memo, ftpd.ForwardingPath.Hops)
if ftpd.ForwardingPath != nil {
if err := ftpd.ForwardingPath.ValidateBasic(); err != nil {
return err
}

// We cannot have non-empty memo and non-empty forwarding path hops at the same time.
if len(ftpd.ForwardingPath.Hops) > 0 && ftpd.Memo != "" {
return errorsmod.Wrapf(ErrInvalidMemo, "memo must be empty if forwarding path hops is not empty: %s, %s", ftpd.Memo, ftpd.ForwardingPath.Hops)
}
}

return nil
Expand Down
106 changes: 93 additions & 13 deletions modules/apps/transfer/types/packet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
)
Expand Down Expand Up @@ -233,15 +234,26 @@ func TestFungibleTokenPacketDataV2ValidateBasic(t *testing.T) {
sender,
receiver,
"",
&types.ForwardingInfo{
Hops: []*types.Hop{
{
PortId: "transfer",
ChannelId: "channel-1",
types.NewForwardingInfo("", validHop, validHop),
),
nil,
},
{
"success: valid packet with forwarding path hops with memo",
types.NewFungibleTokenPacketDataV2(
[]types.Token{
{
Denom: types.Denom{
gjermundgaraba marked this conversation as resolved.
Show resolved Hide resolved
Base: denom,
Trace: []types.Trace{types.NewTrace("transfer", "channel-0"), types.NewTrace("transfer", "channel-1")},
},
Amount: amount,
},
Memo: "",
},
sender,
receiver,
"",
types.NewForwardingInfo("memo", validHop),
),
nil,
},
Expand Down Expand Up @@ -399,15 +411,83 @@ func TestFungibleTokenPacketDataV2ValidateBasic(t *testing.T) {
sender,
receiver,
"memo",
&types.ForwardingInfo{
Hops: []*types.Hop{
{
PortId: "transfer",
ChannelId: "channel-1",
},
types.NewForwardingInfo("", validHop),
),
types.ErrInvalidMemo,
},
{
"failure: invalid forwarding path port ID",
types.NewFungibleTokenPacketDataV2(
[]types.Token{
{
Denom: types.NewDenom(denom, types.NewTrace("transfer", "channel-0"), types.NewTrace("transfer", "channel-1")),
Amount: amount,
},
},
sender,
receiver,
"",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: invalidPort,
ChannelId: "channel-1",
},
),
),
host.ErrInvalidID,
},
{
"failure: invalid forwarding path channel ID",
types.NewFungibleTokenPacketDataV2(
[]types.Token{
{
Denom: types.NewDenom(denom, types.NewTrace("transfer", "channel-0"), types.NewTrace("transfer", "channel-1")),
Amount: amount,
},
},
sender,
receiver,
"",
types.NewForwardingInfo(
"",
&types.Hop{
PortId: "transfer",
ChannelId: invalidChannel,
},
),
),
host.ErrInvalidID,
},
{
"failure: invalid forwarding path too many hops",
types.NewFungibleTokenPacketDataV2(
[]types.Token{
{
Denom: types.NewDenom(denom, types.NewTrace("transfer", "channel-0"), types.NewTrace("transfer", "channel-1")),
Amount: amount,
},
Memo: "",
},
sender,
receiver,
"",
types.NewForwardingInfo("", generateHops(types.MaximumNumberOfForwardingHops+1)...),
),
types.ErrInvalidForwardingInfo,
},
{
"failure: invalid forwarding path too long memo",
types.NewFungibleTokenPacketDataV2(
[]types.Token{
{
Denom: types.NewDenom(denom, types.NewTrace("transfer", "channel-0"), types.NewTrace("transfer", "channel-1")),
Amount: amount,
},
},
sender,
receiver,
"",
types.NewForwardingInfo(ibctesting.GenerateString(types.MaximumMemoLength+1), validHop),
),
types.ErrInvalidMemo,
},
Expand Down
Loading