diff --git a/modules/apps/29-fee/fee_test.go b/modules/apps/29-fee/fee_test.go new file mode 100644 index 00000000000..9ecb400f710 --- /dev/null +++ b/modules/apps/29-fee/fee_test.go @@ -0,0 +1,41 @@ +package fee_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/ibc-go/modules/apps/29-fee/types" + transfertypes "github.com/cosmos/ibc-go/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/testing" +) + +type FeeTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + path *ibctesting.Path +} + +func (suite *FeeTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + feeTransferVersion := channeltypes.MergeChannelVersions(types.Version, transfertypes.Version) + path.EndpointA.ChannelConfig.Version = feeTransferVersion + path.EndpointB.ChannelConfig.Version = feeTransferVersion + path.EndpointA.ChannelConfig.PortID = transfertypes.PortID + path.EndpointB.ChannelConfig.PortID = transfertypes.PortID + suite.path = path +} + +func TestIBCFeeTestSuite(t *testing.T) { + suite.Run(t, new(FeeTestSuite)) +} diff --git a/modules/apps/29-fee/ibc_module.go b/modules/apps/29-fee/ibc_module.go new file mode 100644 index 00000000000..0c22fb19d8d --- /dev/null +++ b/modules/apps/29-fee/ibc_module.go @@ -0,0 +1,178 @@ +package fee + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + "github.com/cosmos/ibc-go/modules/apps/29-fee/keeper" + "github.com/cosmos/ibc-go/modules/apps/29-fee/types" + channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/modules/core/exported" +) + +// IBCModule implements the ICS26 callbacks for the fee middleware given the fee keeper and the underlying application. +type IBCModule struct { + keeper keeper.Keeper + app porttypes.IBCModule +} + +// NewIBCModule creates a new IBCModule given the keeper and underlying application +func NewIBCModule(k keeper.Keeper, app porttypes.IBCModule) IBCModule { + return IBCModule{ + keeper: k, + app: app, + } +} + +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) error { + mwVersion, appVersion := channeltypes.SplitChannelVersion(version) + // Since it is valid for fee version to not be specified, the above middleware version may be for a middleware + // lower down in the stack. Thus, if it is not a fee version we pass the entire version string onto the underlying + // application. + // If an invalid fee version was passed, we expect the underlying application to fail on its version negotiation. + if mwVersion == types.Version { + im.keeper.SetFeeEnabled(ctx, portID, channelID) + } else { + // middleware version is not the expected version for this midddleware. Pass the full version string along, + // if it not valid version for any other lower middleware, an error will be returned by base application. + appVersion = version + } + + // call underlying app's OnChanOpenInit callback with the appVersion + return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, + chanCap, counterparty, appVersion) +} + +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version, + counterpartyVersion string, +) error { + mwVersion, appVersion := channeltypes.SplitChannelVersion(version) + cpMwVersion, cpAppVersion := channeltypes.SplitChannelVersion(counterpartyVersion) + + // Since it is valid for fee version to not be specified, the above middleware version may be for a middleware + // lower down in the stack. Thus, if it is not a fee version we pass the entire version string onto the underlying + // application. + // If an invalid fee version was passed, we expect the underlying application to fail on its version negotiation. + if mwVersion == types.Version || cpMwVersion == types.Version { + if cpMwVersion != mwVersion { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "fee versions do not match. self version: %s, counterparty version: %s", mwVersion, cpMwVersion) + } + + im.keeper.SetFeeEnabled(ctx, portID, channelID) + } else { + // middleware versions are not the expected version for this midddleware. Pass the full version strings along, + // if it not valid version for any other lower middleware, an error will be returned by base application. + appVersion = version + cpAppVersion = counterpartyVersion + } + + // call underlying app's OnChanOpenTry callback with the app versions + return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, + chanCap, counterparty, appVersion, cpAppVersion) +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // If handshake was initialized with fee enabled it must complete with fee enabled. + // If handshake was initialized with fee disabled it must complete with fee disabled. + cpAppVersion := counterpartyVersion + if im.keeper.IsFeeEnabled(ctx, portID, channelID) { + var cpFeeVersion string + cpFeeVersion, cpAppVersion = channeltypes.SplitChannelVersion(counterpartyVersion) + + if cpFeeVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "expected counterparty version: %s, got: %s", types.Version, cpFeeVersion) + } + } + // call underlying app's OnChanOpenAck callback with the counterparty app version. + return im.app.OnChanOpenAck(ctx, portID, channelID, cpAppVersion) +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // call underlying app's OnChanOpenConfirm callback. + return im.app.OnChanOpenConfirm(ctx, portID, channelID) +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // TODO: Unescrow all remaining funds for unprocessed packets + im.keeper.DeleteFeeEnabled(ctx, portID, channelID) + return im.app.OnChanCloseInit(ctx, portID, channelID) +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // TODO: Unescrow all remaining funds for unprocessed packets + im.keeper.DeleteFeeEnabled(ctx, portID, channelID) + return im.app.OnChanCloseConfirm(ctx, portID, channelID) +} + +// OnRecvPacket implements the IBCModule interface. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + // TODO: Implement fee specific logic if fee is enabled for the given channel + return im.app.OnRecvPacket(ctx, packet, relayer) +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // TODO: Implement fee specific logic if fee is enabled for the given channel + return im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) +} + +// OnTimeoutPacket implements the IBCModule interface +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // TODO: Implement fee specific logic if fee is enabled for the given channel + return im.app.OnTimeoutPacket(ctx, packet, relayer) +} diff --git a/modules/apps/29-fee/ibc_module_test.go b/modules/apps/29-fee/ibc_module_test.go new file mode 100644 index 00000000000..915fdaebf79 --- /dev/null +++ b/modules/apps/29-fee/ibc_module_test.go @@ -0,0 +1,302 @@ +package fee_test + +import ( + "fmt" + + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + "github.com/cosmos/ibc-go/modules/apps/29-fee/types" + transfertypes "github.com/cosmos/ibc-go/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/testing" +) + +// Tests OnChanOpenInit on ChainA +func (suite *FeeTestSuite) TestOnChanOpenInit() { + testCases := []struct { + name string + version string + expPass bool + }{ + { + "valid fee middleware and transfer version", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + true, + }, + { + "fee version not included, only perform transfer logic", + transfertypes.Version, + true, + }, + { + "invalid fee middleware version", + channeltypes.MergeChannelVersions("otherfee28-1", transfertypes.Version), + false, + }, + { + "invalid transfer version", + channeltypes.MergeChannelVersions(types.Version, "wrongics20-1"), + false, + }, + { + "incorrect wrapping delimiter", + fmt.Sprintf("%s//%s", types.Version, transfertypes.Version), + false, + }, + { + "transfer version not wrapped", + types.Version, + false, + }, + { + "hanging delimiter", + fmt.Sprintf("%s:%s:", types.Version, transfertypes.Version), + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + // reset suite + suite.SetupTest() + suite.coordinator.SetupConnections(suite.path) + suite.path.EndpointA.ChannelID = ibctesting.FirstChannelID + + counterparty := channeltypes.NewCounterparty(suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID) + channel := &channeltypes.Channel{ + State: channeltypes.INIT, + Ordering: channeltypes.UNORDERED, + Counterparty: counterparty, + ConnectionHops: []string{suite.path.EndpointA.ConnectionID}, + Version: tc.version, + } + + module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.TransferPort) + suite.Require().NoError(err) + + chanCap, err := suite.chainA.App.GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(ibctesting.TransferPort, suite.path.EndpointA.ChannelID)) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + + err = cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, chanCap, counterparty, channel.Version) + + if tc.expPass { + suite.Require().NoError(err, "unexpected error from version: %s", tc.version) + } else { + suite.Require().Error(err, "error not returned for version: %s", tc.version) + } + }) + } +} + +// Tests OnChanOpenTry on ChainA +func (suite *FeeTestSuite) TestOnChanOpenTry() { + testCases := []struct { + name string + version string + cpVersion string + crossing bool + expPass bool + }{ + { + "valid fee middleware and transfer version", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + false, + true, + }, + { + "valid transfer version on try and counterparty", + transfertypes.Version, + transfertypes.Version, + false, + true, + }, + { + "valid fee middleware and transfer version, crossing hellos", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + true, + true, + }, + { + "invalid fee middleware version", + channeltypes.MergeChannelVersions("otherfee28-1", transfertypes.Version), + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + false, + false, + }, + { + "invalid counterparty fee middleware version", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + channeltypes.MergeChannelVersions("wrongfee29-1", transfertypes.Version), + false, + false, + }, + { + "invalid transfer version", + channeltypes.MergeChannelVersions(types.Version, "wrongics20-1"), + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + false, + false, + }, + { + "invalid counterparty transfer version", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + channeltypes.MergeChannelVersions(types.Version, "wrongics20-1"), + false, + false, + }, + { + "transfer version not wrapped", + types.Version, + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + false, + false, + }, + { + "counterparty transfer version not wrapped", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + types.Version, + false, + false, + }, + { + "fee version not included on try, but included in counterparty", + transfertypes.Version, + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + false, + false, + }, + { + "fee version not included", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + transfertypes.Version, + false, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + // reset suite + suite.SetupTest() + suite.coordinator.SetupConnections(suite.path) + suite.path.EndpointB.ChanOpenInit() + + var ( + chanCap *capabilitytypes.Capability + ok bool + err error + ) + if tc.crossing { + suite.path.EndpointA.ChanOpenInit() + chanCap, ok = suite.chainA.GetSimApp().ScopedTransferKeeper.GetCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(ibctesting.TransferPort, suite.path.EndpointA.ChannelID)) + suite.Require().True(ok) + } else { + chanCap, err = suite.chainA.App.GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(ibctesting.TransferPort, suite.path.EndpointA.ChannelID)) + suite.Require().NoError(err) + } + + suite.path.EndpointA.ChannelID = ibctesting.FirstChannelID + + counterparty := channeltypes.NewCounterparty(suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID) + channel := &channeltypes.Channel{ + State: channeltypes.INIT, + Ordering: channeltypes.UNORDERED, + Counterparty: counterparty, + ConnectionHops: []string{suite.path.EndpointA.ConnectionID}, + Version: tc.version, + } + + module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.TransferPort) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + + err = cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, chanCap, counterparty, tc.version, tc.cpVersion) + + if tc.expPass { + suite.Require().NoError(err, "unexpected error from version: %s", tc.version) + } else { + suite.Require().Error(err, "error not returned for version: %s", tc.version) + } + }) + } +} + +// Tests OnChanOpenAck on ChainA +func (suite *FeeTestSuite) TestOnChanOpenAck() { + testCases := []struct { + name string + cpVersion string + malleate func(suite *FeeTestSuite) + expPass bool + }{ + { + "success", + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + func(suite *FeeTestSuite) {}, + true, + }, + { + "invalid fee version", + channeltypes.MergeChannelVersions("fee29-A", transfertypes.Version), + func(suite *FeeTestSuite) {}, + false, + }, + { + "invalid transfer version", + channeltypes.MergeChannelVersions(types.Version, "ics20-4"), + func(suite *FeeTestSuite) {}, + false, + }, + { + "previous INIT set without fee, however counterparty set fee version", // note this can only happen with incompetent or malicious counterparty chain + channeltypes.MergeChannelVersions(types.Version, transfertypes.Version), + func(suite *FeeTestSuite) { + // do the first steps without fee version, then pass the fee version as counterparty version in ChanOpenACK + suite.path.EndpointA.ChannelConfig.Version = transfertypes.Version + suite.path.EndpointB.ChannelConfig.Version = transfertypes.Version + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + suite.coordinator.SetupConnections(suite.path) + + // malleate test case + tc.malleate(suite) + + suite.path.EndpointA.ChanOpenInit() + suite.path.EndpointB.ChanOpenTry() + + module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.TransferPort) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + + err = cbs.OnChanOpenAck(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, tc.cpVersion) + if tc.expPass { + suite.Require().NoError(err, "unexpected error for case: %s", tc.name) + } else { + suite.Require().Error(err, "%s expected error but returned none", tc.name) + } + }) + } +} diff --git a/modules/apps/29-fee/keeper/keeper.go b/modules/apps/29-fee/keeper/keeper.go index 8dd7ab03af2..eb14e1a3a1f 100644 --- a/modules/apps/29-fee/keeper/keeper.go +++ b/modules/apps/29-fee/keeper/keeper.go @@ -3,12 +3,22 @@ package keeper import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/ibc-go/modules/apps/29-fee/types" + channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/modules/core/exported" +) + +// Middleware must implement types.ChannelKeeper and types.PortKeeper expected interfaces +// so that it can wrap IBC channel and port logic for underlying application. +var ( + _ types.ChannelKeeper = Keeper{} + _ types.PortKeeper = Keeper{} ) // Keeper defines the IBC fungible transfer keeper @@ -18,16 +28,12 @@ type Keeper struct { channelKeeper types.ChannelKeeper portKeeper types.PortKeeper - authKeeper types.AccountKeeper - bankKeeper types.BankKeeper - scopedKeeper capabilitykeeper.ScopedKeeper } // NewKeeper creates a new 29-fee Keeper instance func NewKeeper( cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, - authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, ) Keeper { return Keeper{ @@ -35,9 +41,6 @@ func NewKeeper( storeKey: key, channelKeeper: channelKeeper, portKeeper: portKeeper, - authKeeper: authKeeper, - bankKeeper: bankKeeper, - scopedKeeper: scopedKeeper, } } @@ -46,37 +49,51 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/"+host.ModuleName+"-"+types.ModuleName) } -/* -// IsBound checks if the transfer module is already bound to the desired port -func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { - _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) - return ok +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability { + return k.portKeeper.BindPort(ctx, portID) } -// BindPort defines a wrapper function for the ort Keeper's function in -// order to expose it to module's InitGenesis function -func (k Keeper) BindPort(ctx sdk.Context, portID string) error { - cap := k.portKeeper.BindPort(ctx, portID) - return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +// ChanCloseInit wraps the channel keeper's function in order to expose it to underlying app. +func (k Keeper) ChanCloseInit(ctx sdk.Context, portID, channelID string, chanCap *capabilitytypes.Capability) error { + return k.channelKeeper.ChanCloseInit(ctx, portID, channelID, chanCap) +} + +// GetChannel wraps IBC ChannelKeeper's GetChannel function +func (k Keeper) GetChannel(ctx sdk.Context, portID, channelID string) (channeltypes.Channel, bool) { + return k.channelKeeper.GetChannel(ctx, portID, channelID) } -// SetPort sets the portID for the transfer module. Used in InitGenesis -func (k Keeper) SetPort(ctx sdk.Context, portID string) { +// GetNextSequenceSend wraps IBC ChannelKeeper's GetNextSequenceSend function +func (k Keeper) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) { + return k.channelKeeper.GetNextSequenceSend(ctx, portID, channelID) +} + +// SendPacket wraps IBC ChannelKeeper's SendPacket function +func (k Keeper) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI) error { + return k.channelKeeper.SendPacket(ctx, chanCap, packet) +} + +// SetFeeEnabled sets a flag to determine if fee handling logic should run for the given channel +// identified by channel and port identifiers. +func (k Keeper) SetFeeEnabled(ctx sdk.Context, portID, channelID string) { store := ctx.KVStore(k.storeKey) - store.Set(types.PortKey, []byte(portID)) + store.Set(types.FeeEnabledKey(portID, channelID), []byte{1}) } -// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function -func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { - return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) +// DeleteFeeEnabled deletes the fee enabled flag for a given portID and channelID +func (k Keeper) DeleteFeeEnabled(ctx sdk.Context, portID, channelID string) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.FeeEnabledKey(portID, channelID)) } -// ClaimCapability allows the transfer module that can claim a capability that IBC module -// passes to it -func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { - return k.scopedKeeper.ClaimCapability(ctx, cap, name) +// IsFeeEnabled returns whether fee handling logic should be run for the given port. It will check the +// fee enabled flag for the given port and channel identifiers +func (k Keeper) IsFeeEnabled(ctx sdk.Context, portID, channelID string) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(types.FeeEnabledKey(portID, channelID)) != nil } -*/ // SetCounterpartyAddress maps the destination chain relayer address to the source relayer address // The receiving chain must store the mapping from: address -> counterpartyAddress for the given channel diff --git a/modules/apps/29-fee/module.go b/modules/apps/29-fee/module.go index c0645e3db78..6e73a3b9b08 100644 --- a/modules/apps/29-fee/module.go +++ b/modules/apps/29-fee/module.go @@ -1,37 +1,34 @@ package fee -/* import ( "context" "encoding/json" - "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" - "github.com/cosmos/ibc-go/modules/apps/transfer/client/cli" - "github.com/cosmos/ibc-go/modules/apps/transfer/keeper" - "github.com/cosmos/ibc-go/modules/apps/transfer/simulation" - "github.com/cosmos/ibc-go/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/modules/apps/29-fee/client/cli" + "github.com/cosmos/ibc-go/modules/apps/29-fee/keeper" + "github.com/cosmos/ibc-go/modules/apps/29-fee/types" + + // "github.com/cosmos/ibc-go/modules/apps/29-fee/client/cli" + // "github.com/cosmos/ibc-go/modules/apps/29-fee/simulation" + porttypes "github.com/cosmos/ibc-go/modules/core/05-port/types" - ibcexported "github.com/cosmos/ibc-go/modules/core/exported" ) var ( _ module.AppModule = AppModule{} - _ porttypes.IBCModule = AppModule{} + _ porttypes.IBCModule = IBCModule{} _ module.AppModuleBasic = AppModuleBasic{} ) @@ -48,30 +45,32 @@ func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {} // RegisterInterfaces registers module concrete types into protobuf Any. func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { - types.RegisterInterfaces(registry) + // types.RegisterInterfaces(registry) } // DefaultGenesis returns default genesis state as raw bytes for the ibc -// transfer module. +// 29-fee module. func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { - return cdc.MustMarshalJSON(types.DefaultGenesisState()) + // return cdc.MustMarshalJSON(types.DefaultGenesisState()) + return nil } // ValidateGenesis performs genesis state validation for the 29-fee module. func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { - var gs types.GenesisState - if err := cdc.UnmarshalJSON(bz, &gs); err != nil { - return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) - } + // var gs types.GenesisState + // if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + // return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + // } - return gs.Validate() + // return gs.Validate() + return nil } // RegisterRESTRoutes implements AppModuleBasic interface func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { } -// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the ibc-transfer module. +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for ics29 fee module. func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) } @@ -121,24 +120,25 @@ func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { // RegisterServices registers module services. func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), am.keeper) - types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + // types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + // types.RegisterQueryServer(cfg.QueryServer(), am.keeper) } -// InitGenesis performs genesis initialization for the ibc-transfer module. It returns +// InitGenesis performs genesis initialization for the ibc-29-fee module. It returns // no validator updates. func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { - var genesisState types.GenesisState - cdc.MustUnmarshalJSON(data, &genesisState) - am.keeper.InitGenesis(ctx, genesisState) + // var genesisState types.GenesisState + // cdc.MustUnmarshalJSON(data, &genesisState) + // am.keeper.InitGenesis(ctx, genesisState) return []abci.ValidatorUpdate{} } -// ExportGenesis returns the exported genesis state as raw bytes for the ibc-transfer +// ExportGenesis returns the exported genesis state as raw bytes for the ibc-29-fee // module. func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - gs := am.keeper.ExportGenesis(ctx) - return cdc.MustMarshalJSON(gs) + // gs := am.keeper.ExportGenesis(ctx) + // return cdc.MustMarshalJSON(gs) + return nil } // ConsensusVersion implements AppModule/ConsensusVersion. @@ -155,9 +155,9 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V // AppModuleSimulation functions -// GenerateGenesisState creates a randomized GenState of the transfer module. +// GenerateGenesisState creates a randomized GenState of the 29-fee module. func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) + // simulation.RandomizedGenState(simState) } // ProposalContents doesn't return any content functions for governance proposals. @@ -165,113 +165,18 @@ func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedP return nil } -// RandomizedParams creates randomized ibc-transfer param changes for the simulator. +// RandomizedParams creates randomized ibc-29-fee param changes for the simulator. func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { - return simulation.ParamChanges(r) + // return simulation.ParamChanges(r) + return nil } -// RegisterStoreDecoder registers a decoder for transfer module's types +// RegisterStoreDecoder registers a decoder for 29-fee module's types func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[types.StoreKey] = simulation.NewDecodeStore(am.keeper) + // sdr[types.StoreKey] = simulation.NewDecodeStore(am.keeper) } -// WeightedOperations returns the all the transfer module operations with their respective weights. +// WeightedOperations returns the all the 29-fee module operations with their respective weights. func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { return nil } - -// OnChanOpenInit implements the IBCModule interface -func (am AppModule) OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID string, - channelID string, - chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - version string, -) error { - return nil -} - -// OnChanOpenTry implements the IBCModule interface -func (am AppModule) OnChanOpenTry( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID, - channelID string, - chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - version, - counterpartyVersion string, -) error { - return nil -} - -// OnChanOpenAck implements the IBCModule interface -func (am AppModule) OnChanOpenAck( - ctx sdk.Context, - portID, - channelID string, - counterpartyVersion string, -) error { - return nil -} - -// OnChanOpenConfirm implements the IBCModule interface -func (am AppModule) OnChanOpenConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return nil -} - -// OnChanCloseInit implements the IBCModule interface -func (am AppModule) OnChanCloseInit( - ctx sdk.Context, - portID, - channelID string, -) error { - // Disallow user-initiated channel closing for 29-fee channels - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "user cannot close channel") -} - -// OnChanCloseConfirm implements the IBCModule interface -func (am AppModule) OnChanCloseConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return nil -} - -// OnRecvPacket implements the IBCModule interface. -func (am AppModule) OnRecvPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) ibcexported.Acknowledgement { - return nil -} - -// OnAcknowledgementPacket implements the IBCModule interface -func (am AppModule) OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { - return nil -} - -// OnTimeoutPacket implements the IBCModule interface -func (am AppModule) OnTimeoutPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) error { - return nil -} -*/ diff --git a/modules/apps/29-fee/types/errors.go b/modules/apps/29-fee/types/errors.go index 9ed5755a9a0..5536d326fc0 100644 --- a/modules/apps/29-fee/types/errors.go +++ b/modules/apps/29-fee/types/errors.go @@ -1,8 +1,10 @@ package types import ( -// sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // 29-fee sentinel errors -var () +var ( + ErrInvalidVersion = sdkerrors.Register(ModuleName, 2, "invalid ICS29 middleware version") +) diff --git a/modules/apps/29-fee/types/expected_keepers.go b/modules/apps/29-fee/types/expected_keepers.go index 07f9bce4b8f..8e49bbe8ab0 100644 --- a/modules/apps/29-fee/types/expected_keepers.go +++ b/modules/apps/29-fee/types/expected_keepers.go @@ -2,29 +2,11 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - - connectiontypes "github.com/cosmos/ibc-go/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" ibcexported "github.com/cosmos/ibc-go/modules/core/exported" ) -// AccountKeeper defines the contract required for account APIs. -type AccountKeeper interface { - GetModuleAddress(name string) sdk.AccAddress - GetModuleAccount(ctx sdk.Context, name string) types.ModuleAccountI -} - -// BankKeeper defines the expected bank keeper -type BankKeeper interface { - SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error - MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error -} - // ChannelKeeper defines the expected IBC channel keeper type ChannelKeeper interface { GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) @@ -33,16 +15,6 @@ type ChannelKeeper interface { ChanCloseInit(ctx sdk.Context, portID, channelID string, chanCap *capabilitytypes.Capability) error } -// ClientKeeper defines the expected IBC client keeper -type ClientKeeper interface { - GetClientConsensusState(ctx sdk.Context, clientID string) (connection ibcexported.ConsensusState, found bool) -} - -// ConnectionKeeper defines the expected IBC connection keeper -type ConnectionKeeper interface { - GetConnection(ctx sdk.Context, connectionID string) (connection connectiontypes.ConnectionEnd, found bool) -} - // PortKeeper defines the expected IBC port keeper type PortKeeper interface { BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability diff --git a/modules/apps/29-fee/types/keys.go b/modules/apps/29-fee/types/keys.go index 474f27fb87a..39bbfe5ff85 100644 --- a/modules/apps/29-fee/types/keys.go +++ b/modules/apps/29-fee/types/keys.go @@ -4,7 +4,7 @@ import "fmt" const ( // ModuleName defines the 29-fee name - ModuleName = "ibcfee" + ModuleName = "feeibc" // StoreKey is the store key string for IBC transfer StoreKey = ModuleName @@ -18,10 +18,21 @@ const ( // QuerierRoute is the querier route for IBC transfer QuerierRoute = ModuleName + Version = "fee29-1" + + // FeeEnabledPrefix is the key prefix for storing fee enabled flag + FeeEnabledKeyPrefix = "feeEnabled" + // RelayerAddressKeyPrefix is the key prefix for relayer address mapping RelayerAddressKeyPrefix = "relayerAddress" ) +// FeeEnabledKey returns the key that stores a flag to determine if fee logic should +// be enabled for the given port and channel identifiers. +func FeeEnabledKey(portID, channelID string) []byte { + return []byte(fmt.Sprintf("%s/%s/%s", FeeEnabledKeyPrefix, portID, channelID)) +} + // KeyRelayerAddress returns the key for relayer address -> counteryparty address mapping func KeyRelayerAddress(address string) []byte { return []byte(fmt.Sprintf("%s/%s", RelayerAddressKeyPrefix, address)) diff --git a/modules/apps/transfer/module.go b/modules/apps/transfer/module.go index a9a1aa4f875..90e8456381a 100644 --- a/modules/apps/transfer/module.go +++ b/modules/apps/transfer/module.go @@ -190,6 +190,7 @@ func ValidateTransferChannelParams( ctx sdk.Context, keeper keeper.Keeper, order channeltypes.Order, + counterparty channeltypes.Counterparty, portID string, channelID string, version string, @@ -213,6 +214,10 @@ func ValidateTransferChannelParams( return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) } + if portID != counterparty.PortId { + return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "portID: %s does not match counterparty portID: %s", portID, counterparty.PortId) + } + if version != types.Version { return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) } @@ -230,7 +235,7 @@ func (am AppModule) OnChanOpenInit( counterparty channeltypes.Counterparty, version string, ) error { - if err := ValidateTransferChannelParams(ctx, am.keeper, order, portID, channelID, version); err != nil { + if err := ValidateTransferChannelParams(ctx, am.keeper, order, counterparty, portID, channelID, version); err != nil { return err } @@ -254,7 +259,7 @@ func (am AppModule) OnChanOpenTry( version, counterpartyVersion string, ) error { - if err := ValidateTransferChannelParams(ctx, am.keeper, order, portID, channelID, version); err != nil { + if err := ValidateTransferChannelParams(ctx, am.keeper, order, counterparty, portID, channelID, version); err != nil { return err } diff --git a/modules/apps/transfer/module_test.go b/modules/apps/transfer/module_test.go index 63d610de84a..954babec3f0 100644 --- a/modules/apps/transfer/module_test.go +++ b/modules/apps/transfer/module_test.go @@ -12,9 +12,10 @@ import ( func (suite *TransferTestSuite) TestOnChanOpenInit() { var ( - channel *channeltypes.Channel - path *ibctesting.Path - chanCap *capabilitytypes.Capability + channel *channeltypes.Channel + path *ibctesting.Path + chanCap *capabilitytypes.Capability + counterparty channeltypes.Counterparty ) testCases := []struct { @@ -63,7 +64,7 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() { suite.coordinator.SetupConnections(path) path.EndpointA.ChannelID = ibctesting.FirstChannelID - counterparty := channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + counterparty = channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) channel = &channeltypes.Channel{ State: channeltypes.INIT, Ordering: channeltypes.UNORDERED, @@ -84,7 +85,7 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() { tc.malleate() // explicitly change fields in channel and testChannel err = cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, counterparty, channel.GetVersion(), ) if tc.expPass { @@ -102,6 +103,7 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { channel *channeltypes.Channel chanCap *capabilitytypes.Capability path *ibctesting.Path + counterparty channeltypes.Counterparty counterpartyVersion string ) @@ -157,7 +159,7 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { suite.coordinator.SetupConnections(path) path.EndpointA.ChannelID = ibctesting.FirstChannelID - counterparty := channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + counterparty = channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) channel = &channeltypes.Channel{ State: channeltypes.TRYOPEN, Ordering: channeltypes.UNORDERED, @@ -179,7 +181,7 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { tc.malleate() // explicitly change fields in channel and testChannel err = cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), counterpartyVersion, + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, counterparty, channel.GetVersion(), counterpartyVersion, ) if tc.expPass { diff --git a/modules/core/04-channel/keeper/handshake.go b/modules/core/04-channel/keeper/handshake.go index 5f9d70ad090..adad943b81c 100644 --- a/modules/core/04-channel/keeper/handshake.go +++ b/modules/core/04-channel/keeper/handshake.go @@ -127,7 +127,8 @@ func (k Keeper) ChanOpenTry( channelID := previousChannelID - // empty channel identifier indicates continuing a previous channel handshake + // non-empty channel identifier indicates continuing a previous channel handshake + // where ChanOpenINIT has already been called on the executing chain. if previousChannelID != "" { // channel identifier and connection hop length checked on msg.ValidateBasic() // ensure that the previous channel exists diff --git a/modules/core/04-channel/types/version.go b/modules/core/04-channel/types/version.go new file mode 100644 index 00000000000..a2696d291ed --- /dev/null +++ b/modules/core/04-channel/types/version.go @@ -0,0 +1,27 @@ +package types + +import "strings" + +const ChannelVersionDelimiter = ":" + +// SplitChannelVersion middleware version will split the channel version string +// into the outermost middleware version and the underlying app version. +// It will use the default delimiter `:` for middleware versions. +// In case there's no delimeter, this function returns an empty string for the middleware version (first return argument), +// and the full input as the second underlying app version. +func SplitChannelVersion(version string) (middlewareVersion, appVersion string) { + // only split out the first middleware version + splitVersions := strings.Split(version, ChannelVersionDelimiter) + if len(splitVersions) == 1 { + return "", version + } + middlewareVersion = splitVersions[0] + appVersion = strings.Join(splitVersions[1:], ChannelVersionDelimiter) + return +} + +// MergeChannelVersions merges the provided versions together with the channel version delimiter +// the versions should be passed in from the highest-level middleware to the base application +func MergeChannelVersions(versions ...string) string { + return strings.Join(versions, ChannelVersionDelimiter) +} diff --git a/modules/core/04-channel/types/version_test.go b/modules/core/04-channel/types/version_test.go new file mode 100644 index 00000000000..fda5a570f2f --- /dev/null +++ b/modules/core/04-channel/types/version_test.go @@ -0,0 +1,77 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/modules/core/04-channel/types" +) + +func TestSplitVersions(t *testing.T) { + testCases := []struct { + name string + version string + mwVersion string + appVersion string + }{ + { + "single wrapped middleware", + "fee29-1:ics20-1", + "fee29-1", + "ics20-1", + }, + { + "multiple wrapped middleware", + "fee29-1:whitelist:ics20-1", + "fee29-1", + "whitelist:ics20-1", + }, + { + "no middleware", + "ics20-1", + "", + "ics20-1", + }, + } + + for _, tc := range testCases { + mwVersion, appVersion := types.SplitChannelVersion(tc.version) + require.Equal(t, tc.mwVersion, mwVersion, "middleware version is unexpected for case: %s", tc.name) + require.Equal(t, tc.appVersion, appVersion, "app version is unexpected for case: %s", tc.name) + } +} + +func TestMergeVersions(t *testing.T) { + testCases := []struct { + name string + versions []string + merged string + }{ + { + "single version", + []string{"ics20-1"}, + "ics20-1", + }, + { + "empty version", + []string{}, + "", + }, + { + "two versions", + []string{"fee29-1", "ics20-1"}, + "fee29-1:ics20-1", + }, + { + "multiple versions", + []string{"fee29-1", "whitelist", "ics20-1"}, + "fee29-1:whitelist:ics20-1", + }, + } + + for _, tc := range testCases { + actual := types.MergeChannelVersions(tc.versions...) + require.Equal(t, tc.merged, actual, "merged versions string does not equal expected value") + } +} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 230ad71ae24..83cdf6ff2a7 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -80,6 +80,9 @@ import ( upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + ibcfee "github.com/cosmos/ibc-go/modules/apps/29-fee" + ibcfeekeeper "github.com/cosmos/ibc-go/modules/apps/29-fee/keeper" + ibcfeetypes "github.com/cosmos/ibc-go/modules/apps/29-fee/types" transfer "github.com/cosmos/ibc-go/modules/apps/transfer" ibctransferkeeper "github.com/cosmos/ibc-go/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/modules/apps/transfer/types" @@ -132,6 +135,7 @@ var ( ibcmock.AppModuleBasic{}, authzmodule.AppModuleBasic{}, vesting.AppModuleBasic{}, + ibcfee.AppModuleBasic{}, ) // module account permissions @@ -184,11 +188,13 @@ type SimApp struct { EvidenceKeeper evidencekeeper.Keeper TransferKeeper ibctransferkeeper.Keeper FeeGrantKeeper feegrantkeeper.Keeper + IBCFeeKeeper ibcfeekeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper ScopedTransferKeeper capabilitykeeper.ScopedKeeper ScopedIBCMockKeeper capabilitykeeper.ScopedKeeper + ScopedIBCFeeKeeper capabilitykeeper.ScopedKeeper // the module manager mm *module.Manager @@ -230,7 +236,7 @@ func NewSimApp( minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, - authzkeeper.StoreKey, + authzkeeper.StoreKey, ibcfeetypes.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) @@ -315,22 +321,35 @@ func NewSimApp( &stakingKeeper, govRouter, ) - // Create Transfer Keepers + app.IBCFeeKeeper = ibcfeekeeper.NewKeeper(appCodec, keys[ibcfeetypes.StoreKey], app.GetSubspace(ibcfeetypes.ModuleName), + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + ) + + // Create Transfer Keeper and pass IBCFeeKeeper as expected Channel and PortKeeper + // since fee middleware will wrap the IBCKeeper for underlying application. app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), - app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.IBCFeeKeeper, &app.IBCFeeKeeper, app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, ) transferModule := transfer.NewAppModule(app.TransferKeeper) + // create fee-wrapped transfer module + feeTransferModule := ibcfee.NewIBCModule(app.IBCFeeKeeper, transferModule) + + feeModule := ibcfee.NewAppModule(app.IBCFeeKeeper) // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do // note replicate if you do not need to test core IBC or light clients. - mockModule := ibcmock.NewAppModule(scopedIBCMockKeeper, &app.IBCKeeper.PortKeeper) + // Pass IBCFeeKeeper for PortKeeper since fee middleware will wrap the IBCKeeper for underlying application. + mockModule := ibcmock.NewAppModule(scopedIBCMockKeeper, &app.IBCFeeKeeper) + // create fee wrapped mock module + feeMockModule := ibcfee.NewIBCModule(app.IBCFeeKeeper, mockModule) // Create static IBC router, add transfer route, then set and seal it + // pass in top-level (fully-wrapped) IBCModules to IBC Router ibcRouter := porttypes.NewRouter() - ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) - ibcRouter.AddRoute(ibcmock.ModuleName, mockModule) + ibcRouter.AddRoute(ibctransfertypes.ModuleName, feeTransferModule) + ibcRouter.AddRoute(ibcmock.ModuleName, feeMockModule) app.IBCKeeper.SetRouter(ibcRouter) // create evidence keeper with router @@ -370,6 +389,7 @@ func NewSimApp( params.NewAppModule(app.ParamsKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), transferModule, + feeModule, mockModule, )