From d6ecea7b1d5dfd9945162dc169ebde3ab8a440aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Tue, 9 Aug 2022 15:40:39 +0200 Subject: [PATCH 1/2] fix: prevent blocked addresses from sending ICS 20 transfers (#1907) * fix bug, add test Ensures that a sender account isn't a blocked address Added test cases for MsgTransfer handling * update documentation * move blocked address check to SendTransfer * add changelog entry (cherry picked from commit f891c2949e3c3d92846b04b64ce23551b6528897) # Conflicts: # modules/apps/transfer/keeper/relay_test.go --- CHANGELOG.md | 1 + modules/apps/transfer/keeper/msg_server.go | 3 +- .../apps/transfer/keeper/msg_server_test.go | 71 +++++++++++++++++++ modules/apps/transfer/keeper/relay.go | 6 ++ modules/apps/transfer/keeper/relay_test.go | 19 +++++ testing/chain.go | 6 ++ 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 modules/apps/transfer/keeper/msg_server_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 90d44e9c891..8de77b1b5d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### State Machine Breaking +* (apps/transfer) [\#1907](https://github.com/cosmos/ibc-go/pull/1907) Blocked module account addresses are no longer allowed to send IBC transfers. * (apps/27-interchain-accounts) [\#1882](https://github.com/cosmos/ibc-go/pull/1882) Explicitly check length of interchain account packet data in favour of nil check. ### Improvements diff --git a/modules/apps/transfer/keeper/msg_server.go b/modules/apps/transfer/keeper/msg_server.go index 5d8e5682200..99a06b3e073 100644 --- a/modules/apps/transfer/keeper/msg_server.go +++ b/modules/apps/transfer/keeper/msg_server.go @@ -10,8 +10,6 @@ import ( var _ types.MsgServer = Keeper{} -// See createOutgoingPacket in spec:https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay - // Transfer defines a rpc handler method for MsgTransfer. func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -20,6 +18,7 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types. if err != nil { return nil, err } + if err := k.SendTransfer( ctx, msg.SourcePort, msg.SourceChannel, msg.Token, sender, msg.Receiver, msg.TimeoutHeight, msg.TimeoutTimestamp, ); err != nil { diff --git a/modules/apps/transfer/keeper/msg_server_test.go b/modules/apps/transfer/keeper/msg_server_test.go new file mode 100644 index 00000000000..9d838a99f11 --- /dev/null +++ b/modules/apps/transfer/keeper/msg_server_test.go @@ -0,0 +1,71 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types" +) + +func (suite *KeeperTestSuite) TestMsgTransfer() { + var msg *types.MsgTransfer + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() {}, + true, + }, + { + "invalid sender", + func() { + msg.Sender = "address" + }, + false, + }, + { + "sender is a blocked address", + func() { + msg.Sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName).String() + }, + false, + }, + { + "channel does not exist", + func() { + msg.SourceChannel = "channel-100" + }, + false, + }, + } + + for _, tc := range testCases { + suite.SetupTest() + + path := NewTransferPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + + coin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + msg = types.NewMsgTransfer( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + coin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), + suite.chainB.GetTimeoutHeight(), 0, // only use timeout height + ) + + tc.malleate() + + res, err := suite.chainA.GetSimApp().TransferKeeper.Transfer(sdk.WrapSDKContext(suite.chainA.GetContext()), msg) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().Error(err) + suite.Require().Nil(res) + } + } +} diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index 3c3a5aa6690..c17ebb7f7ab 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -48,6 +48,8 @@ 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' +// +// Note: An IBC Transfer must be initiated using a MsgTransfer via the Transfer rpc handler func (k Keeper) SendTransfer( ctx sdk.Context, sourcePort, @@ -63,6 +65,10 @@ func (k Keeper) SendTransfer( return types.ErrSendDisabled } + if k.bankKeeper.BlockedAddr(sender) { + return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to send funds", sender) + } + sourceChannelEnd, found := k.channelKeeper.GetChannel(ctx, sourcePort, sourceChannel) if !found { return sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", sourcePort, sourceChannel) diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index ce34f316669..cd33af22ba3 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -19,6 +19,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() { var ( amount sdk.Coin path *ibctesting.Path + sender sdk.AccAddress err error ) @@ -58,8 +59,21 @@ func (suite *KeeperTestSuite) TestSendTransfer() { ) suite.chainA.CreateChannelCapability(suite.chainA.GetSimApp().ScopedIBCMockKeeper, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) +<<<<<<< HEAD }, true, false}, +======= + }, true, false, + }, + { + "transfer failed - sender account is blocked", + func() { + suite.coordinator.CreateTransferChannels(path) + amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName) + }, true, false, + }, +>>>>>>> f891c29 (fix: prevent blocked addresses from sending ICS 20 transfers (#1907)) // createOutgoingPacket tests // - source chain {"send coin failed", @@ -91,6 +105,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() { suite.SetupTest() // reset path = NewTransferPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) + sender = suite.chainA.SenderAccount.GetAddress() tc.malleate() @@ -118,7 +133,11 @@ func (suite *KeeperTestSuite) TestSendTransfer() { err = suite.chainA.GetSimApp().TransferKeeper.SendTransfer( suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, amount, +<<<<<<< HEAD suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.NewHeight(0, 110), 0, +======= + sender, suite.chainB.SenderAccount.GetAddress().String(), suite.chainB.GetTimeoutHeight(), 0, +>>>>>>> f891c29 (fix: prevent blocked addresses from sending ICS 20 transfers (#1907)) ) if tc.expPass { diff --git a/testing/chain.go b/testing/chain.go index 28382a6c463..53d66dad276 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -575,3 +575,9 @@ func (chain *TestChain) GetChannelCapability(portID, channelID string) *capabili return cap } + +// GetTimeoutHeight is a convenience function which returns a IBC packet timeout height +// to be used for testing. It returns the current IBC height + 100 blocks +func (chain *TestChain) GetTimeoutHeight() clienttypes.Height { + return clienttypes.NewHeight(clienttypes.ParseChainID(chain.ChainID), uint64(chain.GetContext().BlockHeight())+100) +} From 72bb76a0b827046b46ab31560107851fa0a21bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Tue, 9 Aug 2022 17:17:38 +0200 Subject: [PATCH 2/2] fix conflicts --- modules/apps/transfer/keeper/msg_server_test.go | 2 +- modules/apps/transfer/keeper/relay_test.go | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/modules/apps/transfer/keeper/msg_server_test.go b/modules/apps/transfer/keeper/msg_server_test.go index 9d838a99f11..20bd005c761 100644 --- a/modules/apps/transfer/keeper/msg_server_test.go +++ b/modules/apps/transfer/keeper/msg_server_test.go @@ -3,7 +3,7 @@ package keeper_test import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types" + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" ) func (suite *KeeperTestSuite) TestMsgTransfer() { diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index cd33af22ba3..43a797ac374 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -59,10 +59,6 @@ func (suite *KeeperTestSuite) TestSendTransfer() { ) suite.chainA.CreateChannelCapability(suite.chainA.GetSimApp().ScopedIBCMockKeeper, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) -<<<<<<< HEAD - }, true, false}, - -======= }, true, false, }, { @@ -73,7 +69,6 @@ func (suite *KeeperTestSuite) TestSendTransfer() { sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName) }, true, false, }, ->>>>>>> f891c29 (fix: prevent blocked addresses from sending ICS 20 transfers (#1907)) // createOutgoingPacket tests // - source chain {"send coin failed", @@ -133,11 +128,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() { err = suite.chainA.GetSimApp().TransferKeeper.SendTransfer( suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, amount, -<<<<<<< HEAD - suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.NewHeight(0, 110), 0, -======= sender, suite.chainB.SenderAccount.GetAddress().String(), suite.chainB.GetTimeoutHeight(), 0, ->>>>>>> f891c29 (fix: prevent blocked addresses from sending ICS 20 transfers (#1907)) ) if tc.expPass {