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

Add IBC hooks for every IBC method #2296

Closed
wants to merge 19 commits into from
Closed
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
144 changes: 144 additions & 0 deletions modules/apps/hooks/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package ibc_hooks
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to move this file inside the types folder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that would create an import cycle, as ibc_module.go depends on the hooks and the hooks on the middleware struct defined in that file


import (
// external libraries
sdk "github.com/cosmos/cosmos-sdk/types"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"

// ibc-go
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
ibcexported "github.com/cosmos/ibc-go/v5/modules/core/exported"
)

type Hooks interface{}

type OnChanOpenInitOverrideHooks interface {
OnChanOpenInitOverride(im IBCMiddleware, ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string) (string, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: do you mind to put each argument on a new line (like we do here)? I think it's easier for readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it to only one line specifically for readability (so it's easy to scan which methods are defined on each interface, considering the arguments are the same as for the functions they override), but happy to change it back to one arg per line

}
type OnChanOpenInitBeforeHooks interface {
Copy link
Member

Choose a reason for hiding this comment

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

Why don't the before and after hooks return errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe they should. I can update them to return errors

OnChanOpenInitBeforeHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string)
}
type OnChanOpenInitAfterHooks interface {
OnChanOpenInitAfterHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string, result string, err error)
}

// OnChanOpenTry Hooks
type OnChanOpenTryOverrideHooks interface {
OnChanOpenTryOverride(im IBCMiddleware, ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string) (string, error)
}
type OnChanOpenTryBeforeHooks interface {
OnChanOpenTryBeforeHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string)
}
type OnChanOpenTryAfterHooks interface {
OnChanOpenTryAfterHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string, version string, err error)
}

// OnChanOpenAck Hooks
type OnChanOpenAckOverrideHooks interface {
OnChanOpenAckOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string) error
}
type OnChanOpenAckBeforeHooks interface {
OnChanOpenAckBeforeHook(ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string)
}
type OnChanOpenAckAfterHooks interface {
OnChanOpenAckAfterHook(ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string, err error)
}

// OnChanOpenConfirm Hooks
type OnChanOpenConfirmOverrideHooks interface {
OnChanOpenConfirmOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string) error
}
type OnChanOpenConfirmBeforeHooks interface {
OnChanOpenConfirmBeforeHook(ctx sdk.Context, portID, channelID string)
}
type OnChanOpenConfirmAfterHooks interface {
OnChanOpenConfirmAfterHook(ctx sdk.Context, portID, channelID string, err error)
}

// OnChanCloseInit Hooks
type OnChanCloseInitOverrideHooks interface {
OnChanCloseInitOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string) error
}
type OnChanCloseInitBeforeHooks interface {
OnChanCloseInitBeforeHook(ctx sdk.Context, portID, channelID string)
}
type OnChanCloseInitAfterHooks interface {
OnChanCloseInitAfterHook(ctx sdk.Context, portID, channelID string, err error)
}

// OnChanCloseConfirm Hooks
type OnChanCloseConfirmOverrideHooks interface {
OnChanCloseConfirmOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string) error
}
type OnChanCloseConfirmBeforeHooks interface {
OnChanCloseConfirmBeforeHook(ctx sdk.Context, portID, channelID string)
}
type OnChanCloseConfirmAfterHooks interface {
OnChanCloseConfirmAfterHook(ctx sdk.Context, portID, channelID string, err error)
}

// OnRecvPacket Hooks
type OnRecvPacketOverrideHooks interface {
OnRecvPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement
}
type OnRecvPacketBeforeHooks interface {
OnRecvPacketBeforeHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress)
}
type OnRecvPacketAfterHooks interface {
OnRecvPacketAfterHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, ack ibcexported.Acknowledgement)
}

// OnAcknowledgementPacket Hooks
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 I would change the order and put the OnRecvPacket hooks before the OnAcknowledgementPacket hooks.

type OnAcknowledgementPacketOverrideHooks interface {
OnAcknowledgementPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error
}
type OnAcknowledgementPacketBeforeHooks interface {
OnAcknowledgementPacketBeforeHook(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress)
}
type OnAcknowledgementPacketAfterHooks interface {
OnAcknowledgementPacketAfterHook(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, err error)
}

// OnTimeoutPacket Hooks
type OnTimeoutPacketOverrideHooks interface {
OnTimeoutPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error
}
type OnTimeoutPacketBeforeHooks interface {
OnTimeoutPacketBeforeHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress)
}
type OnTimeoutPacketAfterHooks interface {
OnTimeoutPacketAfterHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, err error)
}

// SendPacket Hooks
type SendPacketOverrideHooks interface {
SendPacketOverride(i ICS4Middleware, ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI) error
}
type SendPacketBeforeHooks interface {
SendPacketBeforeHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI)
}
type SendPacketAfterHooks interface {
SendPacketAfterHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, err error)
}

// WriteAcknowledgement Hooks
type WriteAcknowledgementOverrideHooks interface {
WriteAcknowledgementOverride(i ICS4Middleware, ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement) error
}
type WriteAcknowledgementBeforeHooks interface {
WriteAcknowledgementBeforeHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement)
}
type WriteAcknowledgementAfterHooks interface {
WriteAcknowledgementAfterHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement, err error)
}

// GetAppVersion Hooks
type GetAppVersionOverrideHooks interface {
GetAppVersionOverride(i ICS4Middleware, ctx sdk.Context, portID, channelID string) (string, bool)
}
type GetAppVersionBeforeHooks interface {
GetAppVersionBeforeHook(ctx sdk.Context, portID, channelID string)
}
type GetAppVersionAfterHooks interface {
GetAppVersionAfterHook(ctx sdk.Context, portID, channelID string, result string, success bool)
}
131 changes: 131 additions & 0 deletions modules/apps/hooks/ibc_middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package ibc_hooks_test

import (
"testing"

"github.com/stretchr/testify/suite"

// external libraries
sdk "github.com/cosmos/cosmos-sdk/types"

// ibc-go
"github.com/cosmos/ibc-go/v5/modules/apps/hooks/testutils"
transfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v5/testing"
ibcmock "github.com/cosmos/ibc-go/v5/testing/mock"
)

type HooksTestSuite struct {
suite.Suite

coordinator *ibctesting.Coordinator

chainA *ibctesting.TestChain
chainB *ibctesting.TestChain

path *ibctesting.Path
}

func (suite *HooksTestSuite) SetupTest() {
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3)
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2))
}

func TestIBCHooksTestSuite(t *testing.T) {
suite.Run(t, new(HooksTestSuite))
}

func (suite *HooksTestSuite) CreateMockPacket() channeltypes.Packet {
return channeltypes.NewPacket(
ibcmock.MockPacketData,
suite.chainA.SenderAccount.GetSequence(),
suite.path.EndpointA.ChannelConfig.PortID,
suite.path.EndpointA.ChannelID,
suite.path.EndpointB.ChannelConfig.PortID,
suite.path.EndpointB.ChannelID,
clienttypes.NewHeight(0, 100),
0,
)
}

func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path {
path := ibctesting.NewPath(chainA, chainB)
path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort
path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort
path.EndpointA.ChannelConfig.Version = transfertypes.Version
path.EndpointB.ChannelConfig.Version = transfertypes.Version

return path
}

func (suite *HooksTestSuite) TestOnRecvPacketHooks() {
var (
trace transfertypes.DenomTrace
amount sdk.Int
receiver string
status testutils.Status
)

testCases := []struct {
msg string
malleate func(*testutils.Status)
expPass bool
}{
{"override", func(status *testutils.Status) {
suite.chainB.GetSimApp().HooksMiddleware.Hooks = testutils.TestRecvOverrideHooks{Status: status}
}, true},
{"before and after", func(status *testutils.Status) {
suite.chainB.GetSimApp().HooksMiddleware.Hooks = testutils.TestRecvBeforeAfterHooks{Status: status}
}, true},
}

for _, tc := range testCases {
tc := tc
suite.Run(tc.msg, func() {
suite.SetupTest() // reset

path := NewTransferPath(suite.chainA, suite.chainB)
suite.coordinator.Setup(path)
receiver = suite.chainB.SenderAccount.GetAddress().String() // must be explicitly changed in malleate
status = testutils.Status{}

amount = sdk.NewInt(100) // must be explicitly changed in malleate
seq := uint64(1)

trace = transfertypes.ParseDenomTrace(sdk.DefaultBondDenom)

// send coin from chainA to chainB
transferMsg := transfertypes.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.NewCoin(trace.IBCDenom(), amount), suite.chainA.SenderAccount.GetAddress().String(), receiver, clienttypes.NewHeight(1, 110), 0)
_, err := suite.chainA.SendMsgs(transferMsg)
suite.Require().NoError(err) // message committed

tc.malleate(&status)

data := transfertypes.NewFungibleTokenPacketData(trace.GetFullDenomPath(), amount.String(), suite.chainA.SenderAccount.GetAddress().String(), receiver)
packet := channeltypes.NewPacket(data.GetBytes(), seq, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0)

ack := suite.chainB.GetSimApp().HooksMiddleware.OnRecvPacket(suite.chainB.GetContext(), packet, suite.chainA.SenderAccount.GetAddress())

if tc.expPass {
suite.Require().True(ack.Success())
} else {
suite.Require().False(ack.Success())
}

if _, ok := suite.chainB.GetSimApp().HooksMiddleware.Hooks.(testutils.TestRecvOverrideHooks); ok {
suite.Require().True(status.OverrideRan)
suite.Require().False(status.BeforeRan)
suite.Require().False(status.AfterRan)
}

if _, ok := suite.chainB.GetSimApp().HooksMiddleware.Hooks.(testutils.TestRecvBeforeAfterHooks); ok {
suite.Require().False(status.OverrideRan)
suite.Require().True(status.BeforeRan)
suite.Require().True(status.AfterRan)
}
})
}
}
Loading