From 7efc38404c871a21ebb1bbe6ddb0f65e34bc32a3 Mon Sep 17 00:00:00 2001 From: Damian Nolan Date: Thu, 18 Nov 2021 12:10:47 +0100 Subject: [PATCH] ICA controller/host submodules (#541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * go mod tidy * creating new genesis types for controller and host submodules * removing dead root module code * updating genesis helpers and adding newly generated types * adding interchain-accounts controller submodule * adding interchain-accounts host submodule * updating simapp to include controller and host ica submodules * adding errors returns for disallowed handshake directions, removing embedded app from host module, updating simapp to conform * updating simapp to remove nil arg to ica host ibc module * removing ics4Wrapper arg from ica host submodule * cleaning up module.go for controller and host submodules * removing commented out tests * commit with broken tests to rebase * disabling app version negotation on controller submodule * fixing tests - now passing * various cleanup, godocs and moving code * updating error msgs to conform to pkg split * removing commented out code * adding combined ica genesis, consolidating to single ica AppModule, updating app.go * adding missing godocs * clean up, godocs, rename validate.go -> version.go, move version related funcs * updating godocs and code organization * removing controller module acc, using icatypes module name for module acc in host submodule * correcting panic error msg * Update modules/apps/27-interchain-accounts/controller/ibc_module.go * Update modules/apps/27-interchain-accounts/types/genesis.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * updating logger kvs, and simplifying OnRecvPacket * address nits on error strings and godocs Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/ibc/proto-docs.md | 69 +- go.mod | 2 - .../client/cli/query.go | 52 -- .../{ => controller}/ibc_module.go | 54 +- .../controller/ibc_module_test.go | 255 ++++++++ .../{ => controller/keeper}/genesis.go | 15 +- .../{ => controller/keeper}/genesis_test.go | 34 +- .../controller/keeper/handshake.go | 133 ++++ .../controller/keeper/handshake_test.go | 258 ++++++++ .../controller/keeper/keeper.go | 215 ++++++ .../controller/keeper/keeper_test.go | 314 +++++++++ .../{ => controller}/keeper/relay.go | 80 --- .../controller/keeper/relay_test.go | 201 ++++++ .../controller/types/keys.go | 9 + .../27-interchain-accounts/host/ibc_module.go | 138 ++++ .../{ => host}/ibc_module_test.go | 165 +---- .../host/keeper/genesis.go | 37 ++ .../host/keeper/genesis_test.go | 57 ++ .../{ => host}/keeper/handshake.go | 83 --- .../host/keeper/handshake_test.go | 268 ++++++++ .../{ => host}/keeper/keeper.go | 33 +- .../{ => host}/keeper/keeper_test.go | 76 +-- .../host/keeper/relay.go | 89 +++ .../{ => host}/keeper/relay_test.go | 212 +----- .../27-interchain-accounts/host/types/keys.go | 9 + .../27-interchain-accounts/keeper/account.go | 58 -- .../keeper/account_test.go | 69 -- .../keeper/grpc_query.go | 34 - .../keeper/grpc_query_test.go | 80 --- .../keeper/handshake_test.go | 465 ------------- modules/apps/27-interchain-accounts/module.go | 91 ++- .../27-interchain-accounts/types/account.go | 79 +-- .../types/account_test.go | 74 --- .../27-interchain-accounts/types/codec.go | 5 + .../27-interchain-accounts/types/errors.go | 2 +- .../27-interchain-accounts/types/genesis.go | 39 +- .../types/genesis.pb.go | 614 +++++++++++++++++- .../apps/27-interchain-accounts/types/keys.go | 57 -- .../27-interchain-accounts/types/keys_test.go | 131 ---- .../27-interchain-accounts/types/packet.go | 1 + .../apps/27-interchain-accounts/types/port.go | 78 +++ .../27-interchain-accounts/types/port_test.go | 169 +++++ .../27-interchain-accounts/types/query.pb.go | 584 ----------------- .../types/{validate.go => version.go} | 16 + .../{validate_test.go => version_test.go} | 45 ++ .../interchain_accounts/v1/genesis.proto | 13 + .../interchain_accounts/v1/query.proto | 30 - testing/simapp/app.go | 88 ++- 48 files changed, 3205 insertions(+), 2475 deletions(-) delete mode 100644 modules/apps/27-interchain-accounts/client/cli/query.go rename modules/apps/27-interchain-accounts/{ => controller}/ibc_module.go (76%) create mode 100644 modules/apps/27-interchain-accounts/controller/ibc_module_test.go rename modules/apps/27-interchain-accounts/{ => controller/keeper}/genesis.go (63%) rename modules/apps/27-interchain-accounts/{ => controller/keeper}/genesis_test.go (51%) create mode 100644 modules/apps/27-interchain-accounts/controller/keeper/handshake.go create mode 100644 modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go create mode 100644 modules/apps/27-interchain-accounts/controller/keeper/keeper.go create mode 100644 modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go rename modules/apps/27-interchain-accounts/{ => controller}/keeper/relay.go (54%) create mode 100644 modules/apps/27-interchain-accounts/controller/keeper/relay_test.go create mode 100644 modules/apps/27-interchain-accounts/controller/types/keys.go create mode 100644 modules/apps/27-interchain-accounts/host/ibc_module.go rename modules/apps/27-interchain-accounts/{ => host}/ibc_module_test.go (69%) create mode 100644 modules/apps/27-interchain-accounts/host/keeper/genesis.go create mode 100644 modules/apps/27-interchain-accounts/host/keeper/genesis_test.go rename modules/apps/27-interchain-accounts/{ => host}/keeper/handshake.go (65%) create mode 100644 modules/apps/27-interchain-accounts/host/keeper/handshake_test.go rename modules/apps/27-interchain-accounts/{ => host}/keeper/keeper.go (84%) rename modules/apps/27-interchain-accounts/{ => host}/keeper/keeper_test.go (73%) create mode 100644 modules/apps/27-interchain-accounts/host/keeper/relay.go rename modules/apps/27-interchain-accounts/{ => host}/keeper/relay_test.go (65%) create mode 100644 modules/apps/27-interchain-accounts/host/types/keys.go delete mode 100644 modules/apps/27-interchain-accounts/keeper/account.go delete mode 100644 modules/apps/27-interchain-accounts/keeper/account_test.go delete mode 100644 modules/apps/27-interchain-accounts/keeper/grpc_query.go delete mode 100644 modules/apps/27-interchain-accounts/keeper/grpc_query_test.go delete mode 100644 modules/apps/27-interchain-accounts/keeper/handshake_test.go create mode 100644 modules/apps/27-interchain-accounts/types/port.go create mode 100644 modules/apps/27-interchain-accounts/types/port_test.go delete mode 100644 modules/apps/27-interchain-accounts/types/query.pb.go rename modules/apps/27-interchain-accounts/types/{validate.go => version.go} (72%) rename modules/apps/27-interchain-accounts/types/{validate_test.go => version_test.go} (60%) delete mode 100644 proto/ibc/applications/interchain_accounts/v1/query.proto diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index 8deda5b71e8..da269933b46 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -9,15 +9,11 @@ - [ibc/applications/interchain_accounts/v1/genesis.proto](#ibc/applications/interchain_accounts/v1/genesis.proto) - [ActiveChannel](#ibc.applications.interchain_accounts.v1.ActiveChannel) + - [ControllerGenesisState](#ibc.applications.interchain_accounts.v1.ControllerGenesisState) - [GenesisState](#ibc.applications.interchain_accounts.v1.GenesisState) + - [HostGenesisState](#ibc.applications.interchain_accounts.v1.HostGenesisState) - [RegisteredInterchainAccount](#ibc.applications.interchain_accounts.v1.RegisteredInterchainAccount) -- [ibc/applications/interchain_accounts/v1/query.proto](#ibc/applications/interchain_accounts/v1/query.proto) - - [QueryInterchainAccountAddressRequest](#ibc.applications.interchain_accounts.v1.QueryInterchainAccountAddressRequest) - - [QueryInterchainAccountAddressResponse](#ibc.applications.interchain_accounts.v1.QueryInterchainAccountAddressResponse) - - - [Query](#ibc.applications.interchain_accounts.v1.Query) - - [ibc/applications/interchain_accounts/v1/types.proto](#ibc/applications/interchain_accounts/v1/types.proto) - [CosmosTx](#ibc.applications.interchain_accounts.v1.CosmosTx) - [InterchainAccountPacketData](#ibc.applications.interchain_accounts.v1.InterchainAccountPacketData) @@ -329,10 +325,10 @@ ActiveChannel contains a pairing of port ID and channel ID for an active interch - + -### GenesisState -GenesisState defines the interchain accounts genesis state +### ControllerGenesisState +ControllerGenesisState defines the interchain accounts controller genesis state | Field | Type | Label | Description | @@ -346,62 +342,49 @@ GenesisState defines the interchain accounts genesis state - + -### RegisteredInterchainAccount -RegisteredInterchainAccount contains a pairing of controller port ID and associated interchain account address +### GenesisState +GenesisState defines the interchain accounts genesis state | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| `port_id` | [string](#string) | | | -| `account_address` | [string](#string) | | | - - - +| `controller_genesis_state` | [ControllerGenesisState](#ibc.applications.interchain_accounts.v1.ControllerGenesisState) | | | +| `host_genesis_state` | [HostGenesisState](#ibc.applications.interchain_accounts.v1.HostGenesisState) | | | - - - - + - - -

Top

- -## ibc/applications/interchain_accounts/v1/query.proto - - - - - -### QueryInterchainAccountAddressRequest -Query request for an interchain account address +### HostGenesisState +HostGenesisState defines the interchain accounts host genesis state | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| `counterparty_port_id` | [string](#string) | | Counterparty PortID is the portID on the controller chain | +| `active_channels` | [ActiveChannel](#ibc.applications.interchain_accounts.v1.ActiveChannel) | repeated | | +| `interchain_accounts` | [RegisteredInterchainAccount](#ibc.applications.interchain_accounts.v1.RegisteredInterchainAccount) | repeated | | +| `port` | [string](#string) | | | - + -### QueryInterchainAccountAddressResponse -Query response for an interchain account address +### RegisteredInterchainAccount +RegisteredInterchainAccount contains a pairing of controller port ID and associated interchain account address | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| `interchain_account_address` | [string](#string) | | The corresponding interchain account address on the host chain | +| `port_id` | [string](#string) | | | +| `account_address` | [string](#string) | | | @@ -413,16 +396,6 @@ Query response for an interchain account address - - - -### Query -Query defines the gRPC querier service. - -| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | -| ----------- | ------------ | ------------- | ------------| ------- | -------- | -| `InterchainAccountAddress` | [QueryInterchainAccountAddressRequest](#ibc.applications.interchain_accounts.v1.QueryInterchainAccountAddressRequest) | [QueryInterchainAccountAddressResponse](#ibc.applications.interchain_accounts.v1.QueryInterchainAccountAddressResponse) | Query to get the address of an interchain account | | - diff --git a/go.mod b/go.mod index 748b42ede11..274ba00ded6 100644 --- a/go.mod +++ b/go.mod @@ -94,7 +94,6 @@ require ( github.com/prometheus/common v0.29.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect - github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rs/cors v1.7.0 // indirect github.com/rs/zerolog v1.23.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect @@ -115,7 +114,6 @@ require ( golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.6 // indirect gopkg.in/ini.v1 v1.63.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect nhooyr.io/websocket v1.8.6 // indirect ) diff --git a/modules/apps/27-interchain-accounts/client/cli/query.go b/modules/apps/27-interchain-accounts/client/cli/query.go deleted file mode 100644 index 70137749561..00000000000 --- a/modules/apps/27-interchain-accounts/client/cli/query.go +++ /dev/null @@ -1,52 +0,0 @@ -package cli - -import ( - "context" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/spf13/cobra" - - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" -) - -func GetQueryCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "interchain-accounts", - Aliases: []string{"ica"}, - Short: "Querying commands for the interchain accounts module", - DisableFlagParsing: true, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, - } - - cmd.AddCommand(GetInterchainAccountCmd()) - - return cmd -} - -func GetInterchainAccountCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "address [counterparty-port-id]", - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - counterpartyPortID := args[0] - - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.InterchainAccountAddress(context.Background(), &types.QueryInterchainAccountAddressRequest{CounterpartyPortId: counterpartyPortID}) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - } - - flags.AddQueryFlagsToCmd(cmd) - - return cmd -} diff --git a/modules/apps/27-interchain-accounts/ibc_module.go b/modules/apps/27-interchain-accounts/controller/ibc_module.go similarity index 76% rename from modules/apps/27-interchain-accounts/ibc_module.go rename to modules/apps/27-interchain-accounts/controller/ibc_module.go index ce5af58f4c8..31e6b59a3be 100644 --- a/modules/apps/27-interchain-accounts/ibc_module.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module.go @@ -1,24 +1,24 @@ -package interchain_accounts +package controller 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/v2/modules/apps/27-interchain-accounts/keeper" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/controller/keeper" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v2/modules/core/05-port/types" ibcexported "github.com/cosmos/ibc-go/v2/modules/core/exported" ) -// IBCModule implements the ICS26 interface for interchain accounts given the -// interchain account keeper and underlying application. +// IBCModule implements the ICS26 interface for interchain accounts controller chains type IBCModule struct { keeper keeper.Keeper app porttypes.IBCModule } -// NewIBCModule creates a new IBCModule given the keeper and underlying application +// NewIBCModule creates a new IBCModule given the associated keeper and underlying application func NewIBCModule(k keeper.Keeper, app porttypes.IBCModule) IBCModule { return IBCModule{ keeper: k, @@ -26,13 +26,12 @@ func NewIBCModule(k keeper.Keeper, app porttypes.IBCModule) IBCModule { } } -// OnChanOpenInit implements the IBCModule interface. Interchain Accounts is -// implemented to act as middleware for connected authentication modules on +// OnChanOpenInit implements the IBCModule interface +// +// Interchain Accounts is implemented to act as middleware for connected authentication modules on // the controller side. The connected modules may not change the controller side portID or // version. They will be allowed to perform custom logic without changing // the parameters stored within a channel struct. -// -// Controller Chain func (im IBCModule) OnChanOpenInit( ctx sdk.Context, order channeltypes.Order, @@ -53,8 +52,6 @@ func (im IBCModule) OnChanOpenInit( } // OnChanOpenTry implements the IBCModule interface -// -// Host Chain func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, @@ -66,16 +63,15 @@ func (im IBCModule) OnChanOpenTry( version, counterpartyVersion string, ) error { - return im.keeper.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version, counterpartyVersion) + return sdkerrors.Wrap(types.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") } -// OnChanOpenAck implements the IBCModule interface. Interchain Accounts is -// implemented to act as middleware for connected authentication modules on +// OnChanOpenAck implements the IBCModule interface +// +// Interchain Accounts is implemented to act as middleware for connected authentication modules on // the controller side. The connected modules may not change the portID or // version. They will be allowed to perform custom logic without changing // the parameters stored within a channel struct. -// -// Controller Chain func (im IBCModule) OnChanOpenAck( ctx sdk.Context, portID, @@ -91,14 +87,12 @@ func (im IBCModule) OnChanOpenAck( } // OnChanOpenAck implements the IBCModule interface -// -// Host Chain func (im IBCModule) OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, ) error { - return im.keeper.OnChanOpenConfirm(ctx, portID, channelID) + return sdkerrors.Wrap(types.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") } // OnChanCloseInit implements the IBCModule interface @@ -121,31 +115,15 @@ func (im IBCModule) OnChanCloseConfirm( } // OnRecvPacket implements the IBCModule interface -// -// Host Chain func (im IBCModule) OnRecvPacket( ctx sdk.Context, packet channeltypes.Packet, _ sdk.AccAddress, ) ibcexported.Acknowledgement { - ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) - - // only attempt the application logic if the packet data - // was successfully decoded - if ack.Success() { - err := im.keeper.OnRecvPacket(ctx, packet) - if err != nil { - ack = channeltypes.NewErrorAcknowledgement(err.Error()) - } - } - - // NOTE: acknowledgement will be written synchronously during IBC handler execution. - return ack + return channeltypes.NewErrorAcknowledgement("cannot receive packet on controller chain") } // OnAcknowledgementPacket implements the IBCModule interface -// -// Controller Chain func (im IBCModule) OnAcknowledgementPacket( ctx sdk.Context, packet channeltypes.Packet, @@ -157,8 +135,6 @@ func (im IBCModule) OnAcknowledgementPacket( } // OnTimeoutPacket implements the IBCModule interface -// -// Controller Chain func (im IBCModule) OnTimeoutPacket( ctx sdk.Context, packet channeltypes.Packet, @@ -180,5 +156,5 @@ func (im IBCModule) NegotiateAppVersion( counterparty channeltypes.Counterparty, proposedVersion string, ) (string, error) { - return im.keeper.NegotiateAppVersion(ctx, order, connectionID, portID, counterparty, proposedVersion) + return "", sdkerrors.Wrap(types.ErrInvalidChannelFlow, "ICS-27 app version negotiation is unsupported on controller chains") } diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go new file mode 100644 index 00000000000..7cec17d38da --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -0,0 +1,255 @@ +package controller_test + +import ( + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v2/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v2/testing" +) + +var ( + // TestAccAddress defines a resuable bech32 address for testing purposes + // TODO: update crypto.AddressHash() when sdk uses address.Module() + TestAccAddress = types.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(types.ModuleName))), TestPortID) + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + // TestPortID defines a resuable port identifier for testing purposes + TestPortID, _ = types.GeneratePortID(TestOwnerAddress, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + // TestVersion defines a resuable interchainaccounts version string for testing purposes + TestVersion = types.NewAppVersion(types.VersionPrefix, TestAccAddress.String()) +) + +type InterchainAccountsTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func TestICATestSuite(t *testing.T) { + suite.Run(t, new(InterchainAccountsTestSuite)) +} + +func (suite *InterchainAccountsTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +func NewICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = types.PortID + path.EndpointB.ChannelConfig.PortID = types.PortID + path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + path.EndpointA.ChannelConfig.Version = types.VersionPrefix + path.EndpointB.ChannelConfig.Version = TestVersion + + return path +} + +func InitInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { + portID, err := types.GeneratePortID(owner, endpoint.ConnectionID, endpoint.Counterparty.ConnectionID) + if err != nil { + return err + } + + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) + + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, owner); err != nil { + return err + } + + // commit state changes for proof verification + endpoint.Chain.App.Commit() + endpoint.Chain.NextBlock() + + // update port/channel ids + endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + endpoint.ChannelConfig.PortID = portID + + return nil +} + +// SetupICAPath invokes the InterchainAccounts entrypoint and subsequent channel handshake handlers +func SetupICAPath(path *ibctesting.Path, owner string) error { + if err := InitInterchainAccount(path.EndpointA, owner); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenTry(); err != nil { + return err + } + + if err := path.EndpointA.ChanOpenAck(); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenConfirm(); err != nil { + return err + } + + return nil +} + +func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() { + var ( + channel *channeltypes.Channel + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", func() {}, true, + }, + { + "ICA OnChanOpenInit fails - UNORDERED channel", func() { + channel.Ordering = channeltypes.UNORDERED + }, false, + }, + { + "ICA auth module callback fails", func() { + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string, + portID, channelID string, chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, version string, + ) error { + return fmt.Errorf("mock ica auth fails") + } + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + // mock init interchain account + portID, err := types.GeneratePortID(TestOwnerAddress, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) + suite.Require().NoError(err) + + portCap := suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), portID) + suite.chainA.GetSimApp().ICAControllerKeeper.ClaimCapability(suite.chainA.GetContext(), portCap, host.PortPath(portID)) + + path.EndpointA.ChannelConfig.PortID = portID + path.EndpointA.ChannelID = ibctesting.FirstChannelID + + // default values + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.INIT, + Ordering: channeltypes.ORDERED, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: types.VersionPrefix, + } + + tc.malleate() // malleate mutates test data + + // ensure channel on chainA is set in state + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, *channel) + + module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + suite.Require().NoError(err) + + chanCap, err := suite.chainA.App.GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(path.EndpointA.ChannelConfig.PortID, 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(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { + var ( + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", func() {}, true, + }, + { + "ICA OnChanOpenACK fails - invalid version", func() { + path.EndpointB.ChannelConfig.Version = "invalid|version" + }, false, + }, + { + "ICA auth module callback fails", func() { + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenAck = func( + ctx sdk.Context, portID, channelID string, counterpartyVersion string, + ) error { + return fmt.Errorf("mock ica auth fails") + } + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module) + suite.Require().True(ok) + + err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.Version) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + }) + } + +} diff --git a/modules/apps/27-interchain-accounts/genesis.go b/modules/apps/27-interchain-accounts/controller/keeper/genesis.go similarity index 63% rename from modules/apps/27-interchain-accounts/genesis.go rename to modules/apps/27-interchain-accounts/controller/keeper/genesis.go index dae97a236b7..8e372000e73 100644 --- a/modules/apps/27-interchain-accounts/genesis.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/genesis.go @@ -1,17 +1,16 @@ -package interchain_accounts +package keeper import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/keeper" "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" host "github.com/cosmos/ibc-go/v2/modules/core/24-host" ) -// InitGenesis initializes the interchain accounts application state from a provided genesis state -func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState) { +// InitGenesis initializes the interchain accounts controller application state from a provided genesis state +func InitGenesis(ctx sdk.Context, keeper Keeper, state types.ControllerGenesisState) { for _, portID := range state.Ports { if !keeper.IsBound(ctx, portID) { cap := keeper.BindPort(ctx, portID) @@ -30,11 +29,11 @@ func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState } } -// ExportGenesis returns the interchain accounts exported genesis -func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState { - return types.NewGenesisState( - keeper.GetAllPorts(ctx), +// ExportGenesis returns the interchain accounts controller exported genesis +func ExportGenesis(ctx sdk.Context, keeper Keeper) *types.ControllerGenesisState { + return types.NewControllerGenesisState( keeper.GetAllActiveChannels(ctx), keeper.GetAllInterchainAccounts(ctx), + keeper.GetAllPorts(ctx), ) } diff --git a/modules/apps/27-interchain-accounts/genesis_test.go b/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go similarity index 51% rename from modules/apps/27-interchain-accounts/genesis_test.go rename to modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go index e403bdcadd4..4e0f749f6d7 100644 --- a/modules/apps/27-interchain-accounts/genesis_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go @@ -1,23 +1,19 @@ -package interchain_accounts_test +package keeper_test import ( - ica "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/controller/keeper" "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v2/testing" ) -func (suite *InterchainAccountsTestSuite) TestInitGenesis() { - var ( - expectedChannelID string = "channel-0" - ) - +func (suite *KeeperTestSuite) TestInitGenesis() { suite.SetupTest() - genesisState := types.GenesisState{ - Ports: []string{types.PortID, TestPortID}, + genesisState := types.ControllerGenesisState{ ActiveChannels: []*types.ActiveChannel{ { PortId: TestPortID, - ChannelId: expectedChannelID, + ChannelId: ibctesting.FirstChannelID, }, }, InterchainAccounts: []*types.RegisteredInterchainAccount{ @@ -26,34 +22,36 @@ func (suite *InterchainAccountsTestSuite) TestInitGenesis() { AccountAddress: TestAccAddress.String(), }, }, + Ports: []string{TestPortID}, } - ica.InitGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAKeeper, genesisState) + keeper.InitGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAControllerKeeper, genesisState) - channelID, found := suite.chainA.GetSimApp().ICAKeeper.GetActiveChannelID(suite.chainA.GetContext(), TestPortID) + channelID, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetActiveChannelID(suite.chainA.GetContext(), TestPortID) suite.Require().True(found) - suite.Require().Equal(expectedChannelID, channelID) + suite.Require().Equal(ibctesting.FirstChannelID, channelID) - accountAdrr, found := suite.chainA.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), TestPortID) + accountAdrr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), TestPortID) suite.Require().True(found) suite.Require().Equal(TestAccAddress.String(), accountAdrr) } -func (suite *InterchainAccountsTestSuite) TestExportGenesis() { +func (suite *KeeperTestSuite) TestExportGenesis() { suite.SetupTest() + path := NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) err := SetupICAPath(path, TestOwnerAddress) suite.Require().NoError(err) - genesisState := ica.ExportGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAKeeper) - - suite.Require().Equal([]string{types.PortID, TestPortID}, genesisState.GetPorts()) + genesisState := keeper.ExportGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAControllerKeeper) suite.Require().Equal(path.EndpointA.ChannelID, genesisState.ActiveChannels[0].ChannelId) suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.ActiveChannels[0].PortId) suite.Require().Equal(TestAccAddress.String(), genesisState.InterchainAccounts[0].AccountAddress) suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.InterchainAccounts[0].PortId) + + suite.Require().Equal([]string{TestPortID}, genesisState.GetPorts()) } diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go new file mode 100644 index 00000000000..6145c867109 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go @@ -0,0 +1,133 @@ +package keeper + +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/v2/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v2/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v2/modules/core/05-port/types" +) + +// OnChanOpenInit performs basic validation of channel initialization. +// The channel order must be ORDERED, the counterparty port identifier +// must be the host chain representation as defined in the types package, +// the channel version must be equal to the version in the types package, +// there must not be an active channel for the specfied port identifier, +// and the interchain accounts module must be able to claim the channel +// capability. +func (k Keeper) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) error { + if order != channeltypes.ORDERED { + return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s", channeltypes.ORDERED, order) + } + + connSequence, err := types.ParseControllerConnSequence(portID) + if err != nil { + return sdkerrors.Wrapf(err, "expected format %s, got %s", types.ControllerPortFormat, portID) + } + + counterpartyConnSequence, err := types.ParseHostConnSequence(portID) + if err != nil { + return sdkerrors.Wrapf(err, "expected format %s, got %s", types.ControllerPortFormat, portID) + } + + if err := k.validateControllerPortParams(ctx, channelID, portID, connSequence, counterpartyConnSequence); err != nil { + return sdkerrors.Wrapf(err, "failed to validate controller port %s", portID) + } + + if counterparty.PortId != types.PortID { + return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "expected %s, got %s", types.PortID, counterparty.PortId) + } + + if version != types.VersionPrefix { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "expected %s, got %s", types.VersionPrefix, version) + } + + activeChannelID, found := k.GetActiveChannelID(ctx, portID) + if found { + return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "existing active channel %s for portID %s", activeChannelID, portID) + } + + return nil +} + +// OnChanOpenAck sets the active channel for the interchain account/owner pair +// and stores the associated interchain account address in state keyed by it's corresponding port identifier +func (k Keeper) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if err := types.ValidateVersion(counterpartyVersion); err != nil { + return sdkerrors.Wrap(err, "counterparty version validation failed") + } + + k.SetActiveChannelID(ctx, portID, channelID) + + accAddr, err := types.ParseAddressFromVersion(counterpartyVersion) + if err != nil { + return sdkerrors.Wrapf(err, "expected format , got %s", types.Delimiter, counterpartyVersion) + } + + k.SetInterchainAccountAddress(ctx, portID, accAddr) + + return nil +} + +// OnChanCloseConfirm removes the active channel stored in state +func (k Keeper) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + + k.DeleteActiveChannelID(ctx, portID) + + return nil +} + +// validateControllerPortParams asserts the provided connection sequence and counterparty connection sequence +// match that of the associated connection stored in state +func (k Keeper) validateControllerPortParams(ctx sdk.Context, channelID, portID string, connectionSeq, counterpartyConnectionSeq uint64) error { + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + if !found { + return sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "port ID %s channel ID %s", portID, channelID) + } + + counterpartyHops, found := k.channelKeeper.CounterpartyHops(ctx, channel) + if !found { + return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + connSeq, err := connectiontypes.ParseConnectionSequence(channel.ConnectionHops[0]) + if err != nil { + return sdkerrors.Wrapf(err, "failed to parse connection sequence %s", channel.ConnectionHops[0]) + } + + counterpartyConnSeq, err := connectiontypes.ParseConnectionSequence(counterpartyHops[0]) + if err != nil { + return sdkerrors.Wrapf(err, "failed to parse counterparty connection sequence %s", counterpartyHops[0]) + } + + if connSeq != connectionSeq { + return sdkerrors.Wrapf(connectiontypes.ErrInvalidConnection, "sequence mismatch, expected %d, got %d", connSeq, connectionSeq) + } + + if counterpartyConnSeq != counterpartyConnectionSeq { + return sdkerrors.Wrapf(connectiontypes.ErrInvalidConnection, "counterparty sequence mismatch, expected %d, got %d", counterpartyConnSeq, counterpartyConnectionSeq) + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go new file mode 100644 index 00000000000..a4bd9622eab --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go @@ -0,0 +1,258 @@ +package keeper_test + +import ( + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v2/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v2/testing" +) + +func (suite *KeeperTestSuite) TestOnChanOpenInit() { + var ( + channel *channeltypes.Channel + path *ibctesting.Path + chanCap *capabilitytypes.Capability + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + + { + "success", + func() { + path.EndpointA.SetChannel(*channel) + }, + true, + }, + { + "invalid order - UNORDERED", + func() { + channel.Ordering = channeltypes.UNORDERED + }, + false, + }, + { + "invalid port ID", + func() { + path.EndpointA.ChannelConfig.PortID = "invalid-port-id" + }, + false, + }, + { + "invalid counterparty port ID", + func() { + path.EndpointA.SetChannel(*channel) + channel.Counterparty.PortId = "invalid-port-id" + }, + false, + }, + { + "invalid version", + func() { + path.EndpointA.SetChannel(*channel) + channel.Version = "version" + }, + false, + }, + { + "channel not found", + func() { + path.EndpointA.ChannelID = "invalid-channel-id" + }, + false, + }, + { + "connection not found", + func() { + channel.ConnectionHops = []string{"invalid-connnection-id"} + path.EndpointA.SetChannel(*channel) + }, + false, + }, + { + "invalid connection sequence", + func() { + portID, err := types.GeneratePortID(TestOwnerAddress, "connection-1", "connection-0") + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.PortID = portID + path.EndpointA.SetChannel(*channel) + }, + false, + }, + { + "invalid counterparty connection sequence", + func() { + portID, err := types.GeneratePortID(TestOwnerAddress, "connection-0", "connection-1") + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.PortID = portID + path.EndpointA.SetChannel(*channel) + }, + false, + }, + { + "channel is already active", + func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + // mock init interchain account + portID, err := types.GeneratePortID(TestOwnerAddress, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) + suite.Require().NoError(err) + + portCap := suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), portID) + suite.chainA.GetSimApp().ICAControllerKeeper.ClaimCapability(suite.chainA.GetContext(), portCap, host.PortPath(portID)) + path.EndpointA.ChannelConfig.PortID = portID + + // default values + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.INIT, + Ordering: channeltypes.ORDERED, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: types.VersionPrefix, + } + + chanCap, err = suite.chainA.App.GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainA.GetSimApp().ICAControllerKeeper.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + }) + } +} + +func (suite *KeeperTestSuite) TestOnChanOpenAck() { + var ( + path *ibctesting.Path + expectedChannelID string + counterpartyVersion string + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", func() {}, true, + }, + { + "invalid counterparty version", + func() { + expectedChannelID = "" + counterpartyVersion = "version" + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + counterpartyVersion = TestVersion + suite.coordinator.SetupConnections(path) + + err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + expectedChannelID = path.EndpointA.ChannelID + + tc.malleate() // malleate mutates test data + + err = suite.chainA.GetSimApp().ICAControllerKeeper.OnChanOpenAck(suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, counterpartyVersion, + ) + + activeChannelID, _ := suite.chainA.GetSimApp().ICAControllerKeeper.GetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + + suite.Require().Equal(activeChannelID, expectedChannelID) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { + var ( + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + + { + "success", func() {}, true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainB.GetSimApp().ICAControllerKeeper.OnChanCloseConfirm(suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + activeChannelID, found := suite.chainB.GetSimApp().ICAControllerKeeper.GetActiveChannelID(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().False(found) + suite.Require().Empty(activeChannelID) + } else { + suite.Require().Error(err) + } + + }) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go new file mode 100644 index 00000000000..08de202dcc8 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go @@ -0,0 +1,215 @@ +package keeper + +import ( + "fmt" + "strings" + + baseapp "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v2/modules/core/24-host" +) + +// Keeper defines the IBC interchain accounts controller keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + + ics4Wrapper types.ICS4Wrapper + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + accountKeeper types.AccountKeeper + + scopedKeeper capabilitykeeper.ScopedKeeper + + msgRouter *baseapp.MsgServiceRouter +} + +// NewKeeper creates a new interchain accounts controller Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, key sdk.StoreKey, + ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, + accountKeeper types.AccountKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, msgRouter *baseapp.MsgServiceRouter, +) Keeper { + return Keeper{ + storeKey: key, + cdc: cdc, + ics4Wrapper: ics4Wrapper, + channelKeeper: channelKeeper, + portKeeper: portKeeper, + accountKeeper: accountKeeper, + scopedKeeper: scopedKeeper, + msgRouter: msgRouter, + } +} + +// Logger returns the application logger, scoped to the associated module +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s-%s", host.ModuleName, types.ModuleName)) +} + +// InitInterchainAccount is the entry point to registering an interchain account. +// It generates a new port identifier using the owner address, connection identifier, +// and counterparty connection identifier. It will bind to the port identifier and +// call 04-channel 'ChanOpenInit'. An error is returned if the port identifier is +// already in use. Gaining access to interchain accounts whose channels have closed +// cannot be done with this function. A regular MsgChanOpenInit must be used. +func (k Keeper) InitInterchainAccount(ctx sdk.Context, connectionID, counterpartyConnectionID, owner string) error { + portID, err := types.GeneratePortID(owner, connectionID, counterpartyConnectionID) + if err != nil { + return err + } + + if k.portKeeper.IsBound(ctx, portID) { + return sdkerrors.Wrap(types.ErrPortAlreadyBound, portID) + } + + cap := k.BindPort(ctx, portID) + if err := k.ClaimCapability(ctx, cap, host.PortPath(portID)); err != nil { + return sdkerrors.Wrap(err, "unable to bind to newly generated portID") + } + + msg := channeltypes.NewMsgChannelOpenInit(portID, types.VersionPrefix, channeltypes.ORDERED, []string{connectionID}, types.PortID, types.ModuleName) + handler := k.msgRouter.Handler(msg) + if _, err := handler(ctx, msg); err != nil { + return err + } + + return nil +} + +// GetAllPorts returns all ports to which the interchain accounts controller module is bound. Used in ExportGenesis +func (k Keeper) GetAllPorts(ctx sdk.Context) []string { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte(types.PortKeyPrefix)) + defer iterator.Close() + + var ports []string + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + ports = append(ports, keySplit[1]) + } + + return ports +} + +// BindPort stores the provided portID and binds to it, returning the associated capability +func (k Keeper) BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability { + store := ctx.KVStore(k.storeKey) + store.Set(types.KeyPort(portID), []byte{0x01}) + + return k.portKeeper.BindPort(ctx, portID) +} + +// IsBound checks if the interchain account controller 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 +} + +// 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) +} + +// ClaimCapability wraps the scopedKeeper's ClaimCapability function +func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { + return k.scopedKeeper.ClaimCapability(ctx, cap, name) +} + +// GetActiveChannelID retrieves the active channelID from the store keyed by the provided portID +func (k Keeper) GetActiveChannelID(ctx sdk.Context, portID string) (string, bool) { + store := ctx.KVStore(k.storeKey) + key := types.KeyActiveChannel(portID) + + if !store.Has(key) { + return "", false + } + + return string(store.Get(key)), true +} + +// GetAllActiveChannels returns a list of all active interchain accounts controller channels and their associated port identifiers +func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []*types.ActiveChannel { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte(types.ActiveChannelKeyPrefix)) + defer iterator.Close() + + var activeChannels []*types.ActiveChannel + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + ch := &types.ActiveChannel{ + PortId: keySplit[1], + ChannelId: string(iterator.Value()), + } + + activeChannels = append(activeChannels, ch) + } + + return activeChannels +} + +// SetActiveChannelID stores the active channelID, keyed by the provided portID +func (k Keeper) SetActiveChannelID(ctx sdk.Context, portID, channelID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.KeyActiveChannel(portID), []byte(channelID)) +} + +// DeleteActiveChannelID removes the active channel keyed by the provided portID stored in state +func (k Keeper) DeleteActiveChannelID(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.KeyActiveChannel(portID)) +} + +// IsActiveChannel returns true if there exists an active channel for the provided portID, otherwise false +func (k Keeper) IsActiveChannel(ctx sdk.Context, portID string) bool { + _, ok := k.GetActiveChannelID(ctx, portID) + return ok +} + +// GetInterchainAccountAddress retrieves the InterchainAccount address from the store keyed by the provided portID +func (k Keeper) GetInterchainAccountAddress(ctx sdk.Context, portID string) (string, bool) { + store := ctx.KVStore(k.storeKey) + key := types.KeyOwnerAccount(portID) + + if !store.Has(key) { + return "", false + } + + return string(store.Get(key)), true +} + +// GetAllInterchainAccounts returns a list of all registered interchain account addresses and their associated controller port identifiers +func (k Keeper) GetAllInterchainAccounts(ctx sdk.Context) []*types.RegisteredInterchainAccount { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte(types.OwnerKeyPrefix)) + + var interchainAccounts []*types.RegisteredInterchainAccount + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + acc := &types.RegisteredInterchainAccount{ + PortId: keySplit[1], + AccountAddress: string(iterator.Value()), + } + + interchainAccounts = append(interchainAccounts, acc) + } + + return interchainAccounts +} + +// SetInterchainAccountAddress stores the InterchainAccount address, keyed by the associated portID +func (k Keeper) SetInterchainAccountAddress(ctx sdk.Context, portID string, address string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.KeyOwnerAccount(portID), []byte(address)) +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go new file mode 100644 index 00000000000..3b5a21e91ec --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go @@ -0,0 +1,314 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v2/testing" +) + +var ( + // TestAccAddress defines a resuable bech32 address for testing purposes + // TODO: update crypto.AddressHash() when sdk uses address.Module() + TestAccAddress = types.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(types.ModuleName))), TestPortID) + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + // TestPortID defines a resuable port identifier for testing purposes + TestPortID, _ = types.GeneratePortID(TestOwnerAddress, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + // TestVersion defines a resuable interchainaccounts version string for testing purposes + TestVersion = types.NewAppVersion(types.VersionPrefix, TestAccAddress.String()) +) + +type KeeperTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func NewICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = types.PortID + path.EndpointB.ChannelConfig.PortID = types.PortID + path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + path.EndpointA.ChannelConfig.Version = types.VersionPrefix + path.EndpointB.ChannelConfig.Version = TestVersion + + return path +} + +// SetupICAPath invokes the InterchainAccounts entrypoint and subsequent channel handshake handlers +func SetupICAPath(path *ibctesting.Path, owner string) error { + if err := InitInterchainAccount(path.EndpointA, owner); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenTry(); err != nil { + return err + } + + if err := path.EndpointA.ChanOpenAck(); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenConfirm(); err != nil { + return err + } + + return nil +} + +// InitInterchainAccount is a helper function for starting the channel handshake +func InitInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { + portID, err := types.GeneratePortID(owner, endpoint.ConnectionID, endpoint.Counterparty.ConnectionID) + if err != nil { + return err + } + + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) + + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, owner); err != nil { + return err + } + + // commit state changes for proof verification + endpoint.Chain.App.Commit() + endpoint.Chain.NextBlock() + + // update port/channel ids + endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + endpoint.ChannelConfig.PortID = portID + + return nil +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestIsBound() { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + isBound := suite.chainA.GetSimApp().ICAControllerKeeper.IsBound(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + suite.Require().True(isBound) +} + +func (suite *KeeperTestSuite) TestGetAllPorts() { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + expectedPorts := []string{TestPortID} + + ports := suite.chainA.GetSimApp().ICAControllerKeeper.GetAllPorts(suite.chainA.GetContext()) + suite.Require().Len(ports, len(expectedPorts)) + suite.Require().Equal(expectedPorts, ports) +} + +func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + counterpartyPortID := path.EndpointA.ChannelConfig.PortID + expectedAddr := authtypes.NewBaseAccountWithAddress(types.GenerateAddress(suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName), counterpartyPortID)).GetAddress() + + retrievedAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), counterpartyPortID) + suite.Require().True(found) + suite.Require().Equal(expectedAddr.String(), retrievedAddr) + + retrievedAddr, found = suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), "invalid port") + suite.Require().False(found) + suite.Require().Empty(retrievedAddr) +} + +func (suite *KeeperTestSuite) TestGetAllActiveChannels() { + var ( + expectedChannelID string = "test-channel" + expectedPortID string = "test-port" + ) + + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), expectedPortID, expectedChannelID) + + expectedChannels := []*types.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: path.EndpointA.ChannelID, + }, + { + PortId: expectedPortID, + ChannelId: expectedChannelID, + }, + } + + activeChannels := suite.chainA.GetSimApp().ICAControllerKeeper.GetAllActiveChannels(suite.chainA.GetContext()) + suite.Require().Len(activeChannels, len(expectedChannels)) + suite.Require().Equal(expectedChannels, activeChannels) +} + +func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() { + var ( + expectedAccAddr string = "test-acc-addr" + expectedPortID string = "test-port" + ) + + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + suite.chainA.GetSimApp().ICAControllerKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), expectedPortID, expectedAccAddr) + + expectedAccounts := []*types.RegisteredInterchainAccount{ + { + PortId: TestPortID, + AccountAddress: TestAccAddress.String(), + }, + { + PortId: expectedPortID, + AccountAddress: expectedAccAddr, + }, + } + + interchainAccounts := suite.chainA.GetSimApp().ICAControllerKeeper.GetAllInterchainAccounts(suite.chainA.GetContext()) + suite.Require().Len(interchainAccounts, len(expectedAccounts)) + suite.Require().Equal(expectedAccounts, interchainAccounts) +} + +func (suite *KeeperTestSuite) TestIsActiveChannel() { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + owner := TestOwnerAddress + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, owner) + suite.Require().NoError(err) + portID := path.EndpointA.ChannelConfig.PortID + + isActive := suite.chainA.GetSimApp().ICAControllerKeeper.IsActiveChannel(suite.chainA.GetContext(), portID) + suite.Require().Equal(isActive, true) +} + +func (suite *KeeperTestSuite) TestSetInterchainAccountAddress() { + var ( + expectedAccAddr string = "test-acc-addr" + expectedPortID string = "test-port" + ) + + suite.chainA.GetSimApp().ICAControllerKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), expectedPortID, expectedAccAddr) + + retrievedAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), expectedPortID) + suite.Require().True(found) + suite.Require().Equal(expectedAccAddr, retrievedAddr) +} + +func (suite *KeeperTestSuite) TestInitInterchainAccount() { + var ( + owner string + path *ibctesting.Path + err error + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", func() {}, true, + }, + { + "port is already bound", + func() { + suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), TestPortID) + }, + false, + }, + { + "fails to generate port-id", + func() { + owner = "" + }, + false, + }, + { + "MsgChanOpenInit fails - channel is already active", + func() { + portID, err := types.GeneratePortID(owner, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) + suite.Require().NoError(err) + + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), portID, path.EndpointA.ChannelID) + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + // TODO: Get rid of this? + owner = TestOwnerAddress // must be explicitly changed + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + tc.malleate() // malleate mutates test data + + err = suite.chainA.GetSimApp().ICAControllerKeeper.InitInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, path.EndpointB.ConnectionID, owner) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + }) + } +} diff --git a/modules/apps/27-interchain-accounts/keeper/relay.go b/modules/apps/27-interchain-accounts/controller/keeper/relay.go similarity index 54% rename from modules/apps/27-interchain-accounts/keeper/relay.go rename to modules/apps/27-interchain-accounts/controller/keeper/relay.go index 9b5eec6e2d8..02a79a00e97 100644 --- a/modules/apps/27-interchain-accounts/keeper/relay.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/relay.go @@ -71,86 +71,6 @@ func (k Keeper) createOutgoingPacket( return packet.Sequence, nil } -// AuthenticateTx ensures the provided msgs contain the correct interchain account signer address retrieved -// from state using the provided controller port identifier -func (k Keeper) AuthenticateTx(ctx sdk.Context, msgs []sdk.Msg, portID string) error { - interchainAccountAddr, found := k.GetInterchainAccountAddress(ctx, portID) - if !found { - return sdkerrors.Wrapf(types.ErrInterchainAccountNotFound, "failed to retrieve interchain account on port %s", portID) - } - - for _, msg := range msgs { - for _, signer := range msg.GetSigners() { - if interchainAccountAddr != signer.String() { - return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "unexpected signer address: expected %s, got %s", interchainAccountAddr, signer.String()) - } - } - } - - return nil -} - -func (k Keeper) executeTx(ctx sdk.Context, sourcePort, destPort, destChannel string, msgs []sdk.Msg) error { - if err := k.AuthenticateTx(ctx, msgs, sourcePort); err != nil { - return err - } - - for _, msg := range msgs { - if err := msg.ValidateBasic(); err != nil { - return err - } - } - - // CacheContext returns a new context with the multi-store branched into a cached storage object - // writeCache is called only if all msgs succeed, performing state transitions atomically - cacheCtx, writeCache := ctx.CacheContext() - for _, msg := range msgs { - if _, err := k.executeMsg(cacheCtx, msg); err != nil { - return err - } - } - - writeCache() - - return nil -} - -// It tries to get the handler from router. And, if router exites, it will perform message. -func (k Keeper) executeMsg(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - handler := k.msgRouter.Handler(msg) - if handler == nil { - return nil, types.ErrInvalidRoute - } - - return handler(ctx, msg) -} - -// OnRecvPacket handles a given interchain accounts packet on a destination host chain -func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) error { - var data types.InterchainAccountPacketData - - if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - // UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks - return sdkerrors.Wrapf(types.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data") - } - - switch data.Type { - case types.EXECUTE_TX: - msgs, err := types.DeserializeCosmosTx(k.cdc, data.Data) - if err != nil { - return err - } - - if err = k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs); err != nil { - return err - } - - return nil - default: - return types.ErrUnknownDataType - } -} - // OnTimeoutPacket removes the active channel associated with the provided packet, the underlying channel end is closed // due to the semantics of ORDERED channels func (k Keeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet) error { diff --git a/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go new file mode 100644 index 00000000000..561cc370ddf --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go @@ -0,0 +1,201 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v2/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v2/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v2/testing" +) + +func (suite *KeeperTestSuite) TestTrySendTx() { + var ( + path *ibctesting.Path + packetData types.InterchainAccountPacketData + chanCap *capabilitytypes.Capability + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "success", + func() { + interchainAccountAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))), + } + + data, err := types.SerializeCosmosTx(suite.chainB.GetSimApp().AppCodec(), []sdk.Msg{msg}) + suite.Require().NoError(err) + + packetData = types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: data, + } + }, + true, + }, + { + "success with multiple sdk.Msg", + func() { + interchainAccountAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msgsBankSend := []sdk.Msg{ + &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))), + }, + &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))), + }, + } + + data, err := types.SerializeCosmosTx(suite.chainB.GetSimApp().AppCodec(), msgsBankSend) + suite.Require().NoError(err) + + packetData = types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: data, + } + }, + true, + }, + { + "data is nil", + func() { + packetData = types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: nil, + } + }, + false, + }, + { + "active channel not found", + func() { + path.EndpointA.ChannelConfig.PortID = "invalid-port-id" + }, + false, + }, + { + "channel does not exist", + func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, "channel-100") + }, + false, + }, + { + "sendPacket fails - channel closed", + func() { + err := path.EndpointA.SetChannelClosed() + suite.Require().NoError(err) + }, + false, + }, + { + "invalid channel capability provided", + func() { + chanCap = nil + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + var ok bool + chanCap, ok = suite.chainA.GetSimApp().ScopedICAMockKeeper.GetCapability(path.EndpointA.Chain.GetContext(), host.ChannelCapabilityPath(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + suite.Require().True(ok) + + tc.malleate() // malleate mutates test data + + _, err = suite.chainA.GetSimApp().ICAControllerKeeper.TrySendTx(suite.chainA.GetContext(), chanCap, path.EndpointA.ChannelConfig.PortID, packetData) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestOnTimeoutPacket() { + var ( + path *ibctesting.Path + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "success", + func() {}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + packet := channeltypes.NewPacket( + []byte{}, + 1, + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + err = suite.chainA.GetSimApp().ICAControllerKeeper.OnTimeoutPacket(suite.chainA.GetContext(), packet) + + activeChannelID, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Empty(activeChannelID) + suite.Require().False(found) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/types/keys.go b/modules/apps/27-interchain-accounts/controller/types/keys.go new file mode 100644 index 00000000000..1f17f79de71 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/keys.go @@ -0,0 +1,9 @@ +package types + +const ( + // ModuleName defines the interchain accounts controller module name + ModuleName = "icacontroller" + + // StoreKey is the store key string for the interchain accounts controller module + StoreKey = ModuleName +) diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go new file mode 100644 index 00000000000..a304c121ef0 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -0,0 +1,138 @@ +package host + +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/v2/modules/apps/27-interchain-accounts/host/keeper" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v2/modules/core/exported" +) + +// IBCModule implements the ICS26 interface for interchain accounts host chains +type IBCModule struct { + keeper keeper.Keeper +} + +// NewIBCModule creates a new IBCModule given the associated keeper +func NewIBCModule(k keeper.Keeper) IBCModule { + return IBCModule{ + keeper: k, + } +} + +// 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 { + return sdkerrors.Wrap(types.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") +} + +// 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 { + return im.keeper.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version, counterpartyVersion) +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + return sdkerrors.Wrap(types.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return im.keeper.OnChanOpenConfirm(ctx, portID, channelID) +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // Disallow user-initiated channel closing for interchain account channels + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "user cannot close channel") +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return im.keeper.OnChanCloseConfirm(ctx, portID, channelID) +} + +// OnRecvPacket implements the IBCModule interface +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + _ sdk.AccAddress, +) ibcexported.Acknowledgement { + ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + if err := im.keeper.OnRecvPacket(ctx, packet); err != nil { + ack = channeltypes.NewErrorAcknowledgement(err.Error()) + } + + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return ack +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + return sdkerrors.Wrap(types.ErrInvalidChannelFlow, "cannot receive acknowledgement on a host channel end, a host chain does not send a packet over the channel") +} + +// OnTimeoutPacket implements the IBCModule interface +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + return sdkerrors.Wrap(types.ErrInvalidChannelFlow, "cannot cause a packet timeout on a host channel end, a host chain does not send a packet over the channel") +} + +// NegotiateAppVersion implements the IBCModule interface +func (im IBCModule) NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, +) (string, error) { + return im.keeper.NegotiateAppVersion(ctx, order, connectionID, portID, counterparty, proposedVersion) +} diff --git a/modules/apps/27-interchain-accounts/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go similarity index 69% rename from modules/apps/27-interchain-accounts/ibc_module_test.go rename to modules/apps/27-interchain-accounts/host/ibc_module_test.go index ca010b6ede1..92203c25ea8 100644 --- a/modules/apps/27-interchain-accounts/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -1,4 +1,4 @@ -package interchain_accounts_test +package host_test import ( "fmt" @@ -25,7 +25,7 @@ var ( // TestOwnerAddress defines a reusable bech32 address for testing purposes TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" // TestPortID defines a resuable port identifier for testing purposes - TestPortID, _ = types.GeneratePortID(TestOwnerAddress, "connection-0", "connection-0") + TestPortID, _ = types.GeneratePortID(TestOwnerAddress, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) // TestVersion defines a resuable interchainaccounts version string for testing purposes TestVersion = types.NewAppVersion(types.VersionPrefix, TestAccAddress.String()) ) @@ -67,9 +67,10 @@ func InitInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { if err != nil { return err } + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) - if err := endpoint.Chain.GetSimApp().ICAKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, owner); err != nil { + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, owner); err != nil { return err } @@ -80,6 +81,7 @@ func InitInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { // update port/channel ids endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) endpoint.ChannelConfig.PortID = portID + return nil } @@ -104,93 +106,12 @@ func SetupICAPath(path *ibctesting.Path, owner string) error { return nil } -func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() { - var ( - channel *channeltypes.Channel - ) - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", func() {}, true, - }, - { - "ICA OnChanOpenInit fails - UNORDERED channel", func() { - channel.Ordering = channeltypes.UNORDERED - }, false, - }, - { - "ICA auth module callback fails", func() { - suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string, - portID, channelID string, chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, version string, - ) error { - return fmt.Errorf("mock ica auth fails") - } - }, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path := NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - // mock init interchain account - portID, err := types.GeneratePortID(TestOwnerAddress, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) - suite.Require().NoError(err) - portCap := suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), portID) - suite.chainA.GetSimApp().ICAKeeper.ClaimCapability(suite.chainA.GetContext(), portCap, host.PortPath(portID)) - - path.EndpointA.ChannelConfig.PortID = portID - path.EndpointA.ChannelID = ibctesting.FirstChannelID - - // default values - counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) - channel = &channeltypes.Channel{ - State: channeltypes.INIT, - Ordering: channeltypes.ORDERED, - Counterparty: counterparty, - ConnectionHops: []string{path.EndpointA.ConnectionID}, - Version: types.VersionPrefix, - } - - tc.malleate() - - // ensure channel on chainA is set in state - suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, *channel) - - module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) - suite.Require().NoError(err) - - chanCap, err := suite.chainA.App.GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(path.EndpointA.ChannelConfig.PortID, 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(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), - ) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { var ( + path *ibctesting.Path channel *channeltypes.Channel ) + testCases := []struct { name string malleate func() @@ -224,8 +145,8 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { suite.Run(tc.name, func() { suite.SetupTest() // reset - path := NewICAPath(suite.chainA, suite.chainB) - counterpartyVersion := types.VersionPrefix + + path = NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) @@ -257,7 +178,7 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { suite.Require().True(ok) err = cbs.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), counterpartyVersion, + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), path.EndpointA.ChannelConfig.Version, ) if tc.expPass { @@ -271,70 +192,6 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { } -func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { - var ( - counterpartyVersion string - ) - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", func() {}, true, - }, - { - "ICA OnChanOpenACK fails - invalid version", func() { - counterpartyVersion = "invalid|version" - }, false, - }, - { - "ICA auth module callback fails", func() { - suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenAck = func( - ctx sdk.Context, portID, channelID string, counterpartyVersion string, - ) error { - return fmt.Errorf("mock ica auth fails") - } - }, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path := NewICAPath(suite.chainA, suite.chainB) - counterpartyVersion = TestVersion - suite.coordinator.SetupConnections(path) - - err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) - suite.Require().NoError(err) - - err = path.EndpointB.ChanOpenTry() - suite.Require().NoError(err) - - tc.malleate() - - module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), types.PortID) - suite.Require().NoError(err) - - cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module) - suite.Require().True(ok) - - err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, counterpartyVersion) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - - }) - } - -} - func (suite *InterchainAccountsTestSuite) TestOnChanOpenConfirm() { testCases := []struct { name string @@ -437,7 +294,7 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { // send 100stake to interchain account wallet amount, _ := sdk.ParseCoinsNormalized("100stake") - interchainAccountAddr, _ := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, _ := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) bankMsg := &banktypes.MsgSend{FromAddress: suite.chainB.SenderAccount.GetAddress().String(), ToAddress: interchainAccountAddr, Amount: amount} _, err = suite.chainB.SendMsgs(bankMsg) diff --git a/modules/apps/27-interchain-accounts/host/keeper/genesis.go b/modules/apps/27-interchain-accounts/host/keeper/genesis.go new file mode 100644 index 00000000000..7a321b88b37 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/genesis.go @@ -0,0 +1,37 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + host "github.com/cosmos/ibc-go/v2/modules/core/24-host" +) + +// InitGenesis initializes the interchain accounts host application state from a provided genesis state +func InitGenesis(ctx sdk.Context, keeper Keeper, state types.HostGenesisState) { + if !keeper.IsBound(ctx, state.Port) { + cap := keeper.BindPort(ctx, state.Port) + if err := keeper.ClaimCapability(ctx, cap, host.PortPath(state.Port)); err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + for _, ch := range state.ActiveChannels { + keeper.SetActiveChannelID(ctx, ch.PortId, ch.ChannelId) + } + + for _, acc := range state.InterchainAccounts { + keeper.SetInterchainAccountAddress(ctx, acc.PortId, acc.AccountAddress) + } +} + +// ExportGenesis returns the interchain accounts host exported genesis +func ExportGenesis(ctx sdk.Context, keeper Keeper) *types.HostGenesisState { + return types.NewHostGenesisState( + keeper.GetAllActiveChannels(ctx), + keeper.GetAllInterchainAccounts(ctx), + types.PortID, + ) +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go new file mode 100644 index 00000000000..9d4a0538599 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go @@ -0,0 +1,57 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/host/keeper" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v2/testing" +) + +func (suite *KeeperTestSuite) TestInitGenesis() { + suite.SetupTest() + + genesisState := types.HostGenesisState{ + ActiveChannels: []*types.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + }, + InterchainAccounts: []*types.RegisteredInterchainAccount{ + { + PortId: TestPortID, + AccountAddress: TestAccAddress.String(), + }, + }, + Port: types.PortID, + } + + keeper.InitGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAHostKeeper, genesisState) + + channelID, found := suite.chainA.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainA.GetContext(), TestPortID) + suite.Require().True(found) + suite.Require().Equal(ibctesting.FirstChannelID, channelID) + + accountAdrr, found := suite.chainA.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), TestPortID) + suite.Require().True(found) + suite.Require().Equal(TestAccAddress.String(), accountAdrr) +} + +func (suite *KeeperTestSuite) TestExportGenesis() { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + genesisState := keeper.ExportGenesis(suite.chainB.GetContext(), suite.chainB.GetSimApp().ICAHostKeeper) + + suite.Require().Equal(path.EndpointB.ChannelID, genesisState.ActiveChannels[0].ChannelId) + suite.Require().Equal(path.EndpointB.ChannelConfig.PortID, genesisState.ActiveChannels[0].PortId) + + suite.Require().Equal(TestAccAddress.String(), genesisState.InterchainAccounts[0].AccountAddress) + suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.InterchainAccounts[0].PortId) + + suite.Require().Equal(types.PortID, genesisState.GetPort()) +} diff --git a/modules/apps/27-interchain-accounts/keeper/handshake.go b/modules/apps/27-interchain-accounts/host/keeper/handshake.go similarity index 65% rename from modules/apps/27-interchain-accounts/keeper/handshake.go rename to modules/apps/27-interchain-accounts/host/keeper/handshake.go index a6c69509013..864498137f9 100644 --- a/modules/apps/27-interchain-accounts/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake.go @@ -12,63 +12,8 @@ import ( host "github.com/cosmos/ibc-go/v2/modules/core/24-host" ) -// OnChanOpenInit performs basic validation of channel initialization. -// The channel order must be ORDERED, the counterparty port identifier -// must be the host chain representation as defined in the types package, -// the channel version must be equal to the version in the types package, -// there must not be an active channel for the specfied port identifier, -// and the interchain accounts module must be able to claim the channel -// capability. -// -// Controller Chain -func (k Keeper) OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID string, - channelID string, - chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - version string, -) error { - if order != channeltypes.ORDERED { - return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s", channeltypes.ORDERED, order) - } - - connSequence, err := types.ParseControllerConnSequence(portID) - if err != nil { - return sdkerrors.Wrapf(err, "expected format %s, got %s", types.ControllerPortFormat, portID) - } - - counterpartyConnSequence, err := types.ParseHostConnSequence(portID) - if err != nil { - return sdkerrors.Wrapf(err, "expected format %s, got %s", types.ControllerPortFormat, portID) - } - - if err := k.validateControllerPortParams(ctx, channelID, portID, connSequence, counterpartyConnSequence); err != nil { - return sdkerrors.Wrapf(err, "failed to validate controller port %s", portID) - } - - if counterparty.PortId != types.PortID { - return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "expected %s, got %s", types.PortID, counterparty.PortId) - } - - if version != types.VersionPrefix { - return sdkerrors.Wrapf(types.ErrInvalidVersion, "expected %s, got %s", types.VersionPrefix, version) - } - - activeChannelID, found := k.GetActiveChannelID(ctx, portID) - if found { - return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "existing active channel %s for portID %s", activeChannelID, portID) - } - - return nil -} - // OnChanOpenTry performs basic validation of the ICA channel // and registers a new interchain account (if it doesn't exist). -// -// Host Chain func (k Keeper) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, @@ -133,35 +78,7 @@ func (k Keeper) OnChanOpenTry( return nil } -// OnChanOpenAck sets the active channel for the interchain account/owner pair -// and stores the associated interchain account address in state keyed by it's corresponding port identifier -// -// Controller Chain -func (k Keeper) OnChanOpenAck( - ctx sdk.Context, - portID, - channelID string, - counterpartyVersion string, -) error { - if err := types.ValidateVersion(counterpartyVersion); err != nil { - return sdkerrors.Wrap(err, "counterparty version validation failed") - } - - k.SetActiveChannelID(ctx, portID, channelID) - - accAddr, err := types.ParseAddressFromVersion(counterpartyVersion) - if err != nil { - return sdkerrors.Wrapf(err, "expected format , got %s", types.Delimiter, counterpartyVersion) - } - - k.SetInterchainAccountAddress(ctx, portID, accAddr) - - return nil -} - // OnChanOpenConfirm completes the handshake process by setting the active channel in state on the host chain -// -// Host Chain func (k Keeper) OnChanOpenConfirm( ctx sdk.Context, portID, diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go new file mode 100644 index 00000000000..65b9a159945 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -0,0 +1,268 @@ +package keeper_test + +import ( + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v2/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v2/testing" +) + +func (suite *KeeperTestSuite) TestOnChanOpenTry() { + var ( + channel *channeltypes.Channel + path *ibctesting.Path + chanCap *capabilitytypes.Capability + counterpartyVersion string + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + + { + "success", + func() { + path.EndpointB.SetChannel(*channel) + }, + true, + }, + { + "invalid order - UNORDERED", + func() { + channel.Ordering = channeltypes.UNORDERED + }, + false, + }, + { + "invalid port", + func() { + path.EndpointB.ChannelConfig.PortID = "invalid-port-id" + }, + false, + }, + { + "invalid counterparty port", + func() { + channel.Counterparty.PortId = "invalid-port-id" + }, + false, + }, + { + "channel not found", + func() { + path.EndpointB.ChannelID = "invalid-channel-id" + }, + false, + }, + { + "connection not found", + func() { + channel.ConnectionHops = []string{"invalid-connnection-id"} + path.EndpointB.SetChannel(*channel) + }, + false, + }, + { + "invalid connection sequence", + func() { + portID, err := types.GeneratePortID(TestOwnerAddress, "connection-0", "connection-1") + suite.Require().NoError(err) + + channel.Counterparty.PortId = portID + path.EndpointB.SetChannel(*channel) + }, + false, + }, + { + "invalid counterparty connection sequence", + func() { + portID, err := types.GeneratePortID(TestOwnerAddress, "connection-1", "connection-0") + suite.Require().NoError(err) + + channel.Counterparty.PortId = portID + path.EndpointB.SetChannel(*channel) + }, + false, + }, + { + "invalid version", + func() { + channel.Version = "version" + path.EndpointB.SetChannel(*channel) + }, + false, + }, + { + "invalid counterparty version", + func() { + counterpartyVersion = "version" + path.EndpointB.SetChannel(*channel) + }, + false, + }, + { + "capability already claimed", + func() { + path.EndpointB.SetChannel(*channel) + err := suite.chainB.GetSimApp().ScopedICAHostKeeper.ClaimCapability(suite.chainB.GetContext(), chanCap, host.ChannelCapabilityPath(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) + suite.Require().NoError(err) + }, + false, + }, + { + "invalid account address", + func() { + portID, err := types.GeneratePortID("invalid-owner-addr", "connection-0", "connection-0") + suite.Require().NoError(err) + + channel.Counterparty.PortId = portID + path.EndpointB.SetChannel(*channel) + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + counterpartyVersion = types.VersionPrefix + suite.coordinator.SetupConnections(path) + + err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + // set the channel id on host + channelSequence := path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(path.EndpointB.Chain.GetContext()) + path.EndpointB.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + + // default values + counterparty := channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.TRYOPEN, + Ordering: channeltypes.ORDERED, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointB.ConnectionID}, + Version: TestVersion, + } + + chanCap, err = suite.chainB.App.GetScopedIBCKeeper().NewCapability(suite.chainB.GetContext(), host.ChannelCapabilityPath(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), + counterpartyVersion, + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + }) + } +} + +func (suite *KeeperTestSuite) TestOnChanOpenConfirm() { + var ( + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + + { + "success", func() {}, true, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenConfirm(suite.chainB.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + }) + } +} + +func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { + var ( + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + + { + "success", func() {}, true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainB.GetSimApp().ICAHostKeeper.OnChanCloseConfirm(suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + activeChannelID, found := suite.chainB.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().False(found) + suite.Require().Empty(activeChannelID) + } else { + suite.Require().Error(err) + } + + }) + } +} diff --git a/modules/apps/27-interchain-accounts/keeper/keeper.go b/modules/apps/27-interchain-accounts/host/keeper/keeper.go similarity index 84% rename from modules/apps/27-interchain-accounts/keeper/keeper.go rename to modules/apps/27-interchain-accounts/host/keeper/keeper.go index 2886698b26d..d27962d9d63 100644 --- a/modules/apps/27-interchain-accounts/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" "github.com/tendermint/tendermint/libs/log" @@ -17,12 +18,11 @@ import ( host "github.com/cosmos/ibc-go/v2/modules/core/24-host" ) -// Keeper defines the IBC interchain account keeper +// Keeper defines the IBC interchain accounts host keeper type Keeper struct { storeKey sdk.StoreKey cdc codec.BinaryCodec - ics4Wrapper types.ICS4Wrapper channelKeeper types.ChannelKeeper portKeeper types.PortKeeper accountKeeper types.AccountKeeper @@ -32,10 +32,9 @@ type Keeper struct { msgRouter *baseapp.MsgServiceRouter } -// NewKeeper creates a new interchain account Keeper instance +// NewKeeper creates a new interchain accounts host Keeper instance func NewKeeper( - cdc codec.BinaryCodec, key sdk.StoreKey, - ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, + cdc codec.BinaryCodec, key sdk.StoreKey, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, accountKeeper types.AccountKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, msgRouter *baseapp.MsgServiceRouter, ) Keeper { @@ -47,7 +46,6 @@ func NewKeeper( return Keeper{ storeKey: key, cdc: cdc, - ics4Wrapper: ics4Wrapper, channelKeeper: channelKeeper, portKeeper: portKeeper, accountKeeper: accountKeeper, @@ -61,7 +59,24 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s-%s", host.ModuleName, types.ModuleName)) } -// GetAllPorts returns all ports to which the interchain accounts module is bound. Used in ExportGenesis +// RegisterInterchainAccount attempts to create a new account using the provided address and stores it in state keyed by the provided port identifier +// If an account for the provided address already exists this function returns early (no-op) +func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, accAddr sdk.AccAddress, controllerPortID string) { + if acc := k.accountKeeper.GetAccount(ctx, accAddr); acc != nil { + return + } + + interchainAccount := types.NewInterchainAccount( + authtypes.NewBaseAccountWithAddress(accAddr), + controllerPortID, + ) + + k.accountKeeper.NewAccount(ctx, interchainAccount) + k.accountKeeper.SetAccount(ctx, interchainAccount) + k.SetInterchainAccountAddress(ctx, controllerPortID, interchainAccount.Address) +} + +// GetAllPorts returns all ports to which the interchain accounts host module is bound. Used in ExportGenesis func (k Keeper) GetAllPorts(ctx sdk.Context) []string { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, []byte(types.PortKeyPrefix)) @@ -85,7 +100,7 @@ func (k Keeper) BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capabi return k.portKeeper.BindPort(ctx, portID) } -// IsBound checks if the interchain account module is already bound to the desired port +// IsBound checks if the interchain account host 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 @@ -113,7 +128,7 @@ func (k Keeper) GetActiveChannelID(ctx sdk.Context, portID string) (string, bool return string(store.Get(key)), true } -// GetAllActiveChannels returns a list of all active interchain accounts channels and their associated port identifiers +// GetAllActiveChannels returns a list of all active interchain accounts host channels and their associated port identifiers func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []*types.ActiveChannel { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, []byte(types.ActiveChannelKeyPrefix)) diff --git a/modules/apps/27-interchain-accounts/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go similarity index 73% rename from modules/apps/27-interchain-accounts/keeper/keeper_test.go rename to modules/apps/27-interchain-accounts/host/keeper/keeper_test.go index 92a57e74eec..5285dff0f98 100644 --- a/modules/apps/27-interchain-accounts/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -20,7 +20,7 @@ var ( // TestOwnerAddress defines a reusable bech32 address for testing purposes TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" // TestPortID defines a resuable port identifier for testing purposes - TestPortID, _ = types.GeneratePortID(TestOwnerAddress, "connection-0", "connection-0") + TestPortID, _ = types.GeneratePortID(TestOwnerAddress, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) // TestVersion defines a resuable interchainaccounts version string for testing purposes TestVersion = types.NewAppVersion(types.VersionPrefix, TestAccAddress.String()) ) @@ -77,15 +77,15 @@ func SetupICAPath(path *ibctesting.Path, owner string) error { } // InitInterchainAccount is a helper function for starting the channel handshake -// TODO: parse identifiers from events func InitInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { portID, err := types.GeneratePortID(owner, endpoint.ConnectionID, endpoint.Counterparty.ConnectionID) if err != nil { return err } + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) - if err := endpoint.Chain.GetSimApp().ICAKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, owner); err != nil { + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, owner); err != nil { return err } @@ -96,6 +96,7 @@ func InitInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { // update port/channel ids endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) endpoint.ChannelConfig.PortID = portID + return nil } @@ -104,27 +105,37 @@ func TestKeeperTestSuite(t *testing.T) { } func (suite *KeeperTestSuite) TestIsBound() { - isBound := suite.chainA.GetSimApp().ICAKeeper.IsBound(suite.chainA.GetContext(), types.PortID) + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + isBound := suite.chainB.GetSimApp().ICAHostKeeper.IsBound(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID) suite.Require().True(isBound) } func (suite *KeeperTestSuite) TestGetAllPorts() { suite.SetupTest() + path := NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) err := SetupICAPath(path, TestOwnerAddress) suite.Require().NoError(err) - expectedPorts := []string{types.PortID, TestPortID} + expectedPorts := []string{types.PortID} - ports := suite.chainA.GetSimApp().ICAKeeper.GetAllPorts(suite.chainA.GetContext()) + ports := suite.chainB.GetSimApp().ICAHostKeeper.GetAllPorts(suite.chainB.GetContext()) suite.Require().Len(ports, len(expectedPorts)) suite.Require().Equal(expectedPorts, ports) } func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() { suite.SetupTest() + path := NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) @@ -134,11 +145,11 @@ func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() { counterpartyPortID := path.EndpointA.ChannelConfig.PortID expectedAddr := authtypes.NewBaseAccountWithAddress(types.GenerateAddress(suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName), counterpartyPortID)).GetAddress() - retrievedAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), counterpartyPortID) + retrievedAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), counterpartyPortID) suite.Require().True(found) suite.Require().Equal(expectedAddr.String(), retrievedAddr) - retrievedAddr, found = suite.chainA.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), "invalid port") + retrievedAddr, found = suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), "invalid port") suite.Require().False(found) suite.Require().Empty(retrievedAddr) } @@ -150,18 +161,19 @@ func (suite *KeeperTestSuite) TestGetAllActiveChannels() { ) suite.SetupTest() + path := NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) err := SetupICAPath(path, TestOwnerAddress) suite.Require().NoError(err) - suite.chainA.GetSimApp().ICAKeeper.SetActiveChannelID(suite.chainA.GetContext(), expectedPortID, expectedChannelID) + suite.chainB.GetSimApp().ICAHostKeeper.SetActiveChannelID(suite.chainB.GetContext(), expectedPortID, expectedChannelID) expectedChannels := []*types.ActiveChannel{ { - PortId: TestPortID, - ChannelId: path.EndpointA.ChannelID, + PortId: path.EndpointB.ChannelConfig.PortID, + ChannelId: path.EndpointB.ChannelID, }, { PortId: expectedPortID, @@ -169,7 +181,7 @@ func (suite *KeeperTestSuite) TestGetAllActiveChannels() { }, } - activeChannels := suite.chainA.GetSimApp().ICAKeeper.GetAllActiveChannels(suite.chainA.GetContext()) + activeChannels := suite.chainB.GetSimApp().ICAHostKeeper.GetAllActiveChannels(suite.chainB.GetContext()) suite.Require().Len(activeChannels, len(expectedChannels)) suite.Require().Equal(expectedChannels, activeChannels) } @@ -181,13 +193,14 @@ func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() { ) suite.SetupTest() + path := NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) err := SetupICAPath(path, TestOwnerAddress) suite.Require().NoError(err) - suite.chainA.GetSimApp().ICAKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), expectedPortID, expectedAccAddr) + suite.chainB.GetSimApp().ICAHostKeeper.SetInterchainAccountAddress(suite.chainB.GetContext(), expectedPortID, expectedAccAddr) expectedAccounts := []*types.RegisteredInterchainAccount{ { @@ -200,23 +213,22 @@ func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() { }, } - interchainAccounts := suite.chainA.GetSimApp().ICAKeeper.GetAllInterchainAccounts(suite.chainA.GetContext()) + interchainAccounts := suite.chainB.GetSimApp().ICAHostKeeper.GetAllInterchainAccounts(suite.chainB.GetContext()) suite.Require().Len(interchainAccounts, len(expectedAccounts)) suite.Require().Equal(expectedAccounts, interchainAccounts) } func (suite *KeeperTestSuite) TestIsActiveChannel() { - suite.SetupTest() // reset + suite.SetupTest() + path := NewICAPath(suite.chainA, suite.chainB) - owner := TestOwnerAddress suite.coordinator.SetupConnections(path) - err := suite.SetupICAPath(path, owner) + err := SetupICAPath(path, TestOwnerAddress) suite.Require().NoError(err) - portID := path.EndpointA.ChannelConfig.PortID - isActive := suite.chainA.GetSimApp().ICAKeeper.IsActiveChannel(suite.chainA.GetContext(), portID) - suite.Require().Equal(isActive, true) + isActive := suite.chainB.GetSimApp().ICAHostKeeper.IsActiveChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID) + suite.Require().True(isActive) } func (suite *KeeperTestSuite) TestSetInterchainAccountAddress() { @@ -225,29 +237,9 @@ func (suite *KeeperTestSuite) TestSetInterchainAccountAddress() { expectedPortID string = "test-port" ) - suite.chainA.GetSimApp().ICAKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), expectedPortID, expectedAccAddr) + suite.chainB.GetSimApp().ICAHostKeeper.SetInterchainAccountAddress(suite.chainB.GetContext(), expectedPortID, expectedAccAddr) - retrievedAddr, found := suite.chainA.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), expectedPortID) + retrievedAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), expectedPortID) suite.Require().True(found) suite.Require().Equal(expectedAccAddr, retrievedAddr) } - -func (suite *KeeperTestSuite) SetupICAPath(path *ibctesting.Path, owner string) error { - if err := InitInterchainAccount(path.EndpointA, owner); err != nil { - return err - } - - if err := path.EndpointB.ChanOpenTry(); err != nil { - return err - } - - if err := path.EndpointA.ChanOpenAck(); err != nil { - return err - } - - if err := path.EndpointB.ChanOpenConfirm(); err != nil { - return err - } - - return nil -} diff --git a/modules/apps/27-interchain-accounts/host/keeper/relay.go b/modules/apps/27-interchain-accounts/host/keeper/relay.go new file mode 100644 index 00000000000..3c2a603de94 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/relay.go @@ -0,0 +1,89 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" +) + +// AuthenticateTx ensures the provided msgs contain the correct interchain account signer address retrieved +// from state using the provided controller port identifier +func (k Keeper) AuthenticateTx(ctx sdk.Context, msgs []sdk.Msg, portID string) error { + interchainAccountAddr, found := k.GetInterchainAccountAddress(ctx, portID) + if !found { + return sdkerrors.Wrapf(types.ErrInterchainAccountNotFound, "failed to retrieve interchain account on port %s", portID) + } + + for _, msg := range msgs { + for _, signer := range msg.GetSigners() { + if interchainAccountAddr != signer.String() { + return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "unexpected signer address: expected %s, got %s", interchainAccountAddr, signer.String()) + } + } + } + + return nil +} + +func (k Keeper) executeTx(ctx sdk.Context, sourcePort, destPort, destChannel string, msgs []sdk.Msg) error { + if err := k.AuthenticateTx(ctx, msgs, sourcePort); err != nil { + return err + } + + for _, msg := range msgs { + if err := msg.ValidateBasic(); err != nil { + return err + } + } + + // CacheContext returns a new context with the multi-store branched into a cached storage object + // writeCache is called only if all msgs succeed, performing state transitions atomically + cacheCtx, writeCache := ctx.CacheContext() + for _, msg := range msgs { + if _, err := k.executeMsg(cacheCtx, msg); err != nil { + return err + } + } + + writeCache() + + return nil +} + +// Attempts to get the message handler from the router and if found will then execute the message +func (k Keeper) executeMsg(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + handler := k.msgRouter.Handler(msg) + if handler == nil { + return nil, types.ErrInvalidRoute + } + + return handler(ctx, msg) +} + +// OnRecvPacket handles a given interchain accounts packet on a destination host chain +func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) error { + var data types.InterchainAccountPacketData + + if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + // UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks + return sdkerrors.Wrapf(types.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data") + } + + switch data.Type { + case types.EXECUTE_TX: + msgs, err := types.DeserializeCosmosTx(k.cdc, data.Data) + if err != nil { + return err + } + + if err = k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs); err != nil { + return err + } + + return nil + default: + return types.ErrUnknownDataType + } +} diff --git a/modules/apps/27-interchain-accounts/keeper/relay_test.go b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go similarity index 65% rename from modules/apps/27-interchain-accounts/keeper/relay_test.go rename to modules/apps/27-interchain-accounts/host/keeper/relay_test.go index e7741a8903c..7bb47ed88a6 100644 --- a/modules/apps/27-interchain-accounts/keeper/relay_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go @@ -6,7 +6,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -15,143 +14,9 @@ import ( transfertypes "github.com/cosmos/ibc-go/v2/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v2/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" - host "github.com/cosmos/ibc-go/v2/modules/core/24-host" ibctesting "github.com/cosmos/ibc-go/v2/testing" ) -func (suite *KeeperTestSuite) TestTrySendTx() { - var ( - path *ibctesting.Path - packetData types.InterchainAccountPacketData - chanCap *capabilitytypes.Capability - ) - - testCases := []struct { - msg string - malleate func() - expPass bool - }{ - { - "success", - func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) - suite.Require().True(found) - - msg := &banktypes.MsgSend{ - FromAddress: interchainAccountAddr, - ToAddress: suite.chainB.SenderAccount.GetAddress().String(), - Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))), - } - - data, err := types.SerializeCosmosTx(suite.chainB.GetSimApp().AppCodec(), []sdk.Msg{msg}) - suite.Require().NoError(err) - - packetData = types.InterchainAccountPacketData{ - Type: types.EXECUTE_TX, - Data: data, - } - }, - true, - }, - { - "success with multiple sdk.Msg", - func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) - suite.Require().True(found) - - msgsBankSend := []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: interchainAccountAddr, - ToAddress: suite.chainB.SenderAccount.GetAddress().String(), - Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))), - }, - &banktypes.MsgSend{ - FromAddress: interchainAccountAddr, - ToAddress: suite.chainB.SenderAccount.GetAddress().String(), - Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))), - }, - } - - data, err := types.SerializeCosmosTx(suite.chainB.GetSimApp().AppCodec(), msgsBankSend) - suite.Require().NoError(err) - - packetData = types.InterchainAccountPacketData{ - Type: types.EXECUTE_TX, - Data: data, - } - }, - true, - }, - { - "data is nil", - func() { - packetData = types.InterchainAccountPacketData{ - Type: types.EXECUTE_TX, - Data: nil, - } - }, - false, - }, - { - "active channel not found", - func() { - path.EndpointA.ChannelConfig.PortID = "invalid-port-id" - }, - false, - }, - { - "channel does not exist", - func() { - suite.chainA.GetSimApp().ICAKeeper.SetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, "channel-100") - }, - false, - }, - { - "sendPacket fails - channel closed", - func() { - err := path.EndpointA.SetChannelClosed() - suite.Require().NoError(err) - }, - false, - }, - { - "invalid channel capability provided", - func() { - chanCap = nil - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.msg, func() { - suite.SetupTest() // reset - - path = NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - err := suite.SetupICAPath(path, TestOwnerAddress) - suite.Require().NoError(err) - - var ok bool - chanCap, ok = suite.chainA.GetSimApp().ScopedICAMockKeeper.GetCapability(path.EndpointA.Chain.GetContext(), host.ChannelCapabilityPath(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) - suite.Require().True(ok) - - tc.malleate() // malleate mutates test data - - _, err = suite.chainA.GetSimApp().ICAKeeper.TrySendTx(suite.chainA.GetContext(), chanCap, path.EndpointA.ChannelConfig.PortID, packetData) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - func (suite *KeeperTestSuite) TestOnRecvPacket() { var ( path *ibctesting.Path @@ -166,7 +31,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { { "interchain account successfully executes banktypes.MsgSend", func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) msg := &banktypes.MsgSend{ @@ -190,7 +55,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { { "interchain account successfully executes stakingtypes.MsgDelegate", func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) validatorAddr := (sdk.ValAddress)(suite.chainB.Vals.Validators[0].Address) @@ -215,7 +80,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { { "interchain account successfully executes stakingtypes.MsgDelegate and stakingtypes.MsgUndelegate sequentially", func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) validatorAddr := (sdk.ValAddress)(suite.chainB.Vals.Validators[0].Address) @@ -246,7 +111,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { { "interchain account successfully executes govtypes.MsgSubmitProposal", func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) testProposal := &govtypes.TextProposal{ @@ -278,7 +143,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { { "interchain account successfully executes govtypes.MsgVote", func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) // Populate the gov keeper in advance with an active proposal @@ -314,7 +179,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { { "interchain account successfully executes disttypes.MsgFundCommunityPool", func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) msg := &disttypes.MsgFundCommunityPool{ @@ -337,7 +202,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { { "interchain account successfully executes disttypes.MsgSetWithdrawAddress", func() { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) msg := &disttypes.MsgSetWithdrawAddress{ @@ -366,7 +231,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { suite.coordinator.Setup(transferPath) - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID) suite.Require().True(found) msg := &transfertypes.MsgTransfer{ @@ -476,7 +341,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { path = NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) - err := suite.SetupICAPath(path, TestOwnerAddress) + err := SetupICAPath(path, TestOwnerAddress) suite.Require().NoError(err) suite.fundICAWallet(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000)))) @@ -494,65 +359,10 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { 0, ) - err = suite.chainB.GetSimApp().ICAKeeper.OnRecvPacket(suite.chainB.GetContext(), packet) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *KeeperTestSuite) TestOnTimeoutPacket() { - var ( - path *ibctesting.Path - ) - - testCases := []struct { - msg string - malleate func() - expPass bool - }{ - { - "success", - func() {}, - true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.msg, func() { - suite.SetupTest() // reset - - path = NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - err := suite.SetupICAPath(path, TestOwnerAddress) - suite.Require().NoError(err) - - tc.malleate() // malleate mutates test data - - packet := channeltypes.NewPacket( - []byte{}, - 1, - path.EndpointA.ChannelConfig.PortID, - path.EndpointA.ChannelID, - path.EndpointB.ChannelConfig.PortID, - path.EndpointB.ChannelID, - clienttypes.NewHeight(0, 100), - 0, - ) - - err = suite.chainA.GetSimApp().ICAKeeper.OnTimeoutPacket(suite.chainA.GetContext(), packet) - - activeChannelID, found := suite.chainA.GetSimApp().ICAKeeper.GetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) + err = suite.chainB.GetSimApp().ICAHostKeeper.OnRecvPacket(suite.chainB.GetContext(), packet) if tc.expPass { suite.Require().NoError(err) - suite.Require().Empty(activeChannelID) - suite.Require().False(found) } else { suite.Require().Error(err) } @@ -561,7 +371,7 @@ func (suite *KeeperTestSuite) TestOnTimeoutPacket() { } func (suite *KeeperTestSuite) fundICAWallet(ctx sdk.Context, portID string, amount sdk.Coins) { - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAKeeper.GetInterchainAccountAddress(ctx, portID) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(ctx, portID) suite.Require().True(found) msgBankSend := &banktypes.MsgSend{ diff --git a/modules/apps/27-interchain-accounts/host/types/keys.go b/modules/apps/27-interchain-accounts/host/types/keys.go new file mode 100644 index 00000000000..284284980e4 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/keys.go @@ -0,0 +1,9 @@ +package types + +const ( + // ModuleName defines the interchain accounts host module name + ModuleName = "icahost" + + // StoreKey is the store key string for the interchain accounts host module + StoreKey = ModuleName +) diff --git a/modules/apps/27-interchain-accounts/keeper/account.go b/modules/apps/27-interchain-accounts/keeper/account.go deleted file mode 100644 index 0c14be4ea97..00000000000 --- a/modules/apps/27-interchain-accounts/keeper/account.go +++ /dev/null @@ -1,58 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" - channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" - host "github.com/cosmos/ibc-go/v2/modules/core/24-host" -) - -// InitInterchainAccount is the entry point to registering an interchain account. -// It generates a new port identifier using the owner address, connection identifier, -// and counterparty connection identifier. It will bind to the port identifier and -// call 04-channel 'ChanOpenInit'. An error is returned if the port identifier is -// already in use. Gaining access to interchain accounts whose channels have closed -// cannot be done with this function. A regular MsgChanOpenInit must be used. -func (k Keeper) InitInterchainAccount(ctx sdk.Context, connectionID, counterpartyConnectionID, owner string) error { - portID, err := types.GeneratePortID(owner, connectionID, counterpartyConnectionID) - if err != nil { - return err - } - - if k.portKeeper.IsBound(ctx, portID) { - return sdkerrors.Wrap(types.ErrPortAlreadyBound, portID) - } - - cap := k.BindPort(ctx, portID) - if err := k.ClaimCapability(ctx, cap, host.PortPath(portID)); err != nil { - return sdkerrors.Wrap(err, "unable to bind to newly generated portID") - } - - msg := channeltypes.NewMsgChannelOpenInit(portID, types.VersionPrefix, channeltypes.ORDERED, []string{connectionID}, types.PortID, types.ModuleName) - handler := k.msgRouter.Handler(msg) - if _, err := handler(ctx, msg); err != nil { - return err - } - - return nil -} - -// RegisterInterchainAccount attempts to create a new account using the provided address and stores it in state keyed by the provided port identifier -// If an account for the provided address already exists this function returns early (no-op) -func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, accAddr sdk.AccAddress, controllerPortID string) { - if acc := k.accountKeeper.GetAccount(ctx, accAddr); acc != nil { - return - } - - interchainAccount := types.NewInterchainAccount( - authtypes.NewBaseAccountWithAddress(accAddr), - controllerPortID, - ) - - k.accountKeeper.NewAccount(ctx, interchainAccount) - k.accountKeeper.SetAccount(ctx, interchainAccount) - k.SetInterchainAccountAddress(ctx, controllerPortID, interchainAccount.Address) -} diff --git a/modules/apps/27-interchain-accounts/keeper/account_test.go b/modules/apps/27-interchain-accounts/keeper/account_test.go deleted file mode 100644 index fceea5eb48b..00000000000 --- a/modules/apps/27-interchain-accounts/keeper/account_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package keeper_test - -import ( - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" - ibctesting "github.com/cosmos/ibc-go/v2/testing" -) - -func (suite *KeeperTestSuite) TestInitInterchainAccount() { - var ( - owner string - path *ibctesting.Path - err error - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", func() {}, true, - }, - { - "port is already bound", - func() { - suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), TestPortID) - }, - false, - }, - { - "fails to generate port-id", - func() { - owner = "" - }, - false, - }, - { - "MsgChanOpenInit fails - channel is already active", - func() { - portID, err := types.GeneratePortID(owner, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) - suite.Require().NoError(err) - suite.chainA.GetSimApp().ICAKeeper.SetActiveChannelID(suite.chainA.GetContext(), portID, path.EndpointA.ChannelID) - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - owner = TestOwnerAddress // must be explicitly changed - path = NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - tc.malleate() // explicitly change fields in channel and testChannel - - err = suite.chainA.GetSimApp().ICAKeeper.InitInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, path.EndpointB.ConnectionID, owner) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - - }) - } -} diff --git a/modules/apps/27-interchain-accounts/keeper/grpc_query.go b/modules/apps/27-interchain-accounts/keeper/grpc_query.go deleted file mode 100644 index 1d110916202..00000000000 --- a/modules/apps/27-interchain-accounts/keeper/grpc_query.go +++ /dev/null @@ -1,34 +0,0 @@ -package keeper - -import ( - "context" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" -) - -var _ types.QueryServer = Keeper{} - -// InterchainAccount implements the Query/InterchainAccount gRPC method -func (k Keeper) InterchainAccountAddress(ctx context.Context, req *types.QueryInterchainAccountAddressRequest) (*types.QueryInterchainAccountAddressResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - - if strings.TrimSpace(req.CounterpartyPortId) == "" { - return nil, status.Error(codes.InvalidArgument, "counterparty portID cannot be empty") - } - - interchainAccountAddress, found := k.GetInterchainAccountAddress(sdk.UnwrapSDKContext(ctx), req.CounterpartyPortId) - if !found { - return nil, status.Error(codes.NotFound, sdkerrors.Wrap(types.ErrInterchainAccountNotFound, req.CounterpartyPortId).Error()) - } - - return &types.QueryInterchainAccountAddressResponse{InterchainAccountAddress: interchainAccountAddress}, nil -} diff --git a/modules/apps/27-interchain-accounts/keeper/grpc_query_test.go b/modules/apps/27-interchain-accounts/keeper/grpc_query_test.go deleted file mode 100644 index d514d6bbc34..00000000000 --- a/modules/apps/27-interchain-accounts/keeper/grpc_query_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package keeper_test - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" -) - -func (suite *KeeperTestSuite) TestQueryInterchainAccountAddress() { - var ( - req *types.QueryInterchainAccountAddressRequest - expAddr string - ) - - testCases := []struct { - msg string - malleate func() - expPass bool - }{ - { - "empty request", - func() { - req = nil - }, - false, - }, - { - "invalid counterparty portID", - func() { - req = &types.QueryInterchainAccountAddressRequest{ - CounterpartyPortId: " ", - } - }, - false, - }, - { - "interchain account address not found", - func() { - req = &types.QueryInterchainAccountAddressRequest{ - CounterpartyPortId: "ics-27", - } - }, - false, - }, - { - "success", - func() { - expAddr = authtypes.NewBaseAccountWithAddress(types.GenerateAddress(TestAccAddress, TestPortID)).GetAddress().String() - req = &types.QueryInterchainAccountAddressRequest{ - CounterpartyPortId: TestPortID, - } - - suite.chainA.GetSimApp().ICAKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), TestPortID, expAddr) - }, - true, - }, - } - - for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { - suite.SetupTest() // reset - - tc.malleate() - ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) - - res, err := suite.chainA.GetSimApp().ICAKeeper.InterchainAccountAddress(ctx, req) - - if tc.expPass { - suite.Require().NoError(err) - suite.Require().NotNil(res) - suite.Require().Equal(expAddr, res.GetInterchainAccountAddress()) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/modules/apps/27-interchain-accounts/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/keeper/handshake_test.go deleted file mode 100644 index cf27023831c..00000000000 --- a/modules/apps/27-interchain-accounts/keeper/handshake_test.go +++ /dev/null @@ -1,465 +0,0 @@ -package keeper_test - -import ( - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" - channeltypes "github.com/cosmos/ibc-go/v2/modules/core/04-channel/types" - host "github.com/cosmos/ibc-go/v2/modules/core/24-host" - ibctesting "github.com/cosmos/ibc-go/v2/testing" -) - -func (suite *KeeperTestSuite) TestOnChanOpenInit() { - var ( - channel *channeltypes.Channel - path *ibctesting.Path - chanCap *capabilitytypes.Capability - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - - { - "success", - func() { - path.EndpointA.SetChannel(*channel) - }, - true, - }, - { - "invalid order - UNORDERED", - func() { - channel.Ordering = channeltypes.UNORDERED - }, - false, - }, - { - "invalid port ID", - func() { - path.EndpointA.ChannelConfig.PortID = "invalid-port-id" - }, - false, - }, - { - "invalid counterparty port ID", - func() { - path.EndpointA.SetChannel(*channel) - channel.Counterparty.PortId = "invalid-port-id" - }, - false, - }, - { - "invalid version", - func() { - path.EndpointA.SetChannel(*channel) - channel.Version = "version" - }, - false, - }, - { - "channel not found", - func() { - path.EndpointA.ChannelID = "invalid-channel-id" - }, - false, - }, - { - "connection not found", - func() { - channel.ConnectionHops = []string{"invalid-connnection-id"} - path.EndpointA.SetChannel(*channel) - }, - false, - }, - { - "invalid connection sequence", - func() { - portID, err := types.GeneratePortID(TestOwnerAddress, "connection-1", "connection-0") - suite.Require().NoError(err) - - path.EndpointA.ChannelConfig.PortID = portID - path.EndpointA.SetChannel(*channel) - }, - false, - }, - { - "invalid counterparty connection sequence", - func() { - portID, err := types.GeneratePortID(TestOwnerAddress, "connection-0", "connection-1") - suite.Require().NoError(err) - - path.EndpointA.ChannelConfig.PortID = portID - path.EndpointA.SetChannel(*channel) - }, - false, - }, - { - "channel is already active", - func() { - suite.chainA.GetSimApp().ICAKeeper.SetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path = NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - // mock init interchain account - portID, err := types.GeneratePortID(TestOwnerAddress, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) - suite.Require().NoError(err) - portCap := suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), portID) - suite.chainA.GetSimApp().ICAKeeper.ClaimCapability(suite.chainA.GetContext(), portCap, host.PortPath(portID)) - path.EndpointA.ChannelConfig.PortID = portID - - // default values - counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) - channel = &channeltypes.Channel{ - State: channeltypes.INIT, - Ordering: channeltypes.ORDERED, - Counterparty: counterparty, - ConnectionHops: []string{path.EndpointA.ConnectionID}, - Version: types.VersionPrefix, - } - - chanCap, err = suite.chainA.App.GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(portID, path.EndpointA.ChannelID)) - suite.Require().NoError(err) - - tc.malleate() // explicitly change fields in channel and testChannel - - err = suite.chainA.GetSimApp().ICAKeeper.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), - ) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - - }) - } -} - -func (suite *KeeperTestSuite) TestOnChanOpenTry() { - var ( - channel *channeltypes.Channel - path *ibctesting.Path - chanCap *capabilitytypes.Capability - counterpartyVersion string - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - - { - "success", - func() { - path.EndpointB.SetChannel(*channel) - }, - true, - }, - { - "invalid order - UNORDERED", - func() { - channel.Ordering = channeltypes.UNORDERED - }, - false, - }, - { - "invalid port", - func() { - path.EndpointB.ChannelConfig.PortID = "invalid-port-id" - }, - false, - }, - { - "invalid counterparty port", - func() { - channel.Counterparty.PortId = "invalid-port-id" - }, - false, - }, - { - "channel not found", - func() { - path.EndpointB.ChannelID = "invalid-channel-id" - }, - false, - }, - { - "connection not found", - func() { - channel.ConnectionHops = []string{"invalid-connnection-id"} - path.EndpointB.SetChannel(*channel) - }, - false, - }, - { - "invalid connection sequence", - func() { - portID, err := types.GeneratePortID(TestOwnerAddress, "connection-0", "connection-1") - suite.Require().NoError(err) - - channel.Counterparty.PortId = portID - path.EndpointB.SetChannel(*channel) - }, - false, - }, - { - "invalid counterparty connection sequence", - func() { - portID, err := types.GeneratePortID(TestOwnerAddress, "connection-1", "connection-0") - suite.Require().NoError(err) - - channel.Counterparty.PortId = portID - path.EndpointB.SetChannel(*channel) - }, - false, - }, - { - "invalid version", - func() { - channel.Version = "version" - path.EndpointB.SetChannel(*channel) - }, - false, - }, - { - "invalid counterparty version", - func() { - counterpartyVersion = "version" - path.EndpointB.SetChannel(*channel) - }, - false, - }, - { - "capability already claimed", - func() { - path.EndpointB.SetChannel(*channel) - err := suite.chainB.GetSimApp().ScopedICAKeeper.ClaimCapability(suite.chainB.GetContext(), chanCap, host.ChannelCapabilityPath(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) - suite.Require().NoError(err) - }, - false, - }, - { - "invalid account address", - func() { - portID, err := types.GeneratePortID("invalid-owner-addr", "connection-0", "connection-0") - suite.Require().NoError(err) - - channel.Counterparty.PortId = portID - path.EndpointB.SetChannel(*channel) - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path = NewICAPath(suite.chainA, suite.chainB) - counterpartyVersion = types.VersionPrefix - suite.coordinator.SetupConnections(path) - - err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) - suite.Require().NoError(err) - - // set the channel id on host - channelSequence := path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(path.EndpointB.Chain.GetContext()) - path.EndpointB.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) - - // default values - counterparty := channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) - channel = &channeltypes.Channel{ - State: channeltypes.TRYOPEN, - Ordering: channeltypes.ORDERED, - Counterparty: counterparty, - ConnectionHops: []string{path.EndpointB.ConnectionID}, - Version: TestVersion, - } - - chanCap, err = suite.chainB.App.GetScopedIBCKeeper().NewCapability(suite.chainB.GetContext(), host.ChannelCapabilityPath(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) - suite.Require().NoError(err) - - tc.malleate() // explicitly change fields in channel and testChannel - - err = suite.chainB.GetSimApp().ICAKeeper.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), - counterpartyVersion, - ) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - - }) - } -} - -// ChainA is controller, ChainB is host chain -func (suite *KeeperTestSuite) TestOnChanOpenAck() { - var ( - path *ibctesting.Path - expectedChannelID string - counterpartyVersion string - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", func() {}, true, - }, - { - "invalid counterparty version", func() { - expectedChannelID = "" - counterpartyVersion = "version" - }, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path = NewICAPath(suite.chainA, suite.chainB) - counterpartyVersion = TestVersion - suite.coordinator.SetupConnections(path) - - err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) - suite.Require().NoError(err) - - err = path.EndpointB.ChanOpenTry() - suite.Require().NoError(err) - expectedChannelID = path.EndpointA.ChannelID - - tc.malleate() // explicitly change fields in channel and testChannel - - err = suite.chainA.GetSimApp().ICAKeeper.OnChanOpenAck(suite.chainA.GetContext(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, counterpartyVersion, - ) - - activeChannelID, _ := suite.chainA.GetSimApp().ICAKeeper.GetActiveChannelID(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) - - suite.Require().Equal(activeChannelID, expectedChannelID) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - -// ChainA is controller, ChainB is host chain -func (suite *KeeperTestSuite) TestOnChanOpenConfirm() { - var ( - path *ibctesting.Path - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - - { - "success", func() {}, true, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path = NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) - suite.Require().NoError(err) - - err = path.EndpointB.ChanOpenTry() - suite.Require().NoError(err) - - err = path.EndpointA.ChanOpenAck() - suite.Require().NoError(err) - - tc.malleate() // explicitly change fields in channel and testChannel - - err = suite.chainB.GetSimApp().ICAKeeper.OnChanOpenConfirm(suite.chainB.GetContext(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - - }) - } -} - -func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { - var ( - path *ibctesting.Path - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - - { - "success", func() {}, true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path = NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - err := SetupICAPath(path, TestOwnerAddress) - suite.Require().NoError(err) - - tc.malleate() // explicitly change fields in channel and testChannel - - err = suite.chainB.GetSimApp().ICAKeeper.OnChanCloseConfirm(suite.chainB.GetContext(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) - - activeChannelID, found := suite.chainB.GetSimApp().ICAKeeper.GetActiveChannelID(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID) - - if tc.expPass { - suite.Require().NoError(err) - suite.Require().False(found) - suite.Require().Empty(activeChannelID) - } else { - suite.Require().Error(err) - } - - }) - } -} diff --git a/modules/apps/27-interchain-accounts/module.go b/modules/apps/27-interchain-accounts/module.go index 8e83463aef9..ed3a09c358d 100644 --- a/modules/apps/27-interchain-accounts/module.go +++ b/modules/apps/27-interchain-accounts/module.go @@ -1,4 +1,4 @@ -package interchain_accounts +package ica import ( "encoding/json" @@ -14,20 +14,26 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/runtime" abci "github.com/tendermint/tendermint/abci/types" - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/client/cli" - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/keeper" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/controller" + controllerkeeper "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/controller/keeper" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/host" + hostkeeper "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/host/keeper" "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" porttypes "github.com/cosmos/ibc-go/v2/modules/core/05-port/types" ) var ( _ module.AppModule = AppModule{} - _ porttypes.IBCModule = IBCModule{} _ module.AppModuleBasic = AppModuleBasic{} + + _ porttypes.IBCModule = controller.IBCModule{} + _ porttypes.IBCModule = host.IBCModule{} ) +// AppModuleBasic is the IBC interchain accounts AppModuleBasic type AppModuleBasic struct{} +// Name implements AppModuleBasic interface func (AppModuleBasic) Name() string { return types.ModuleName } @@ -37,60 +43,70 @@ func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { types.RegisterLegacyAminoCodec(cdc) } +// RegisterInterfaces registers module concrete types into protobuf Any +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes for the IBC +// interchain accounts module func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { return cdc.MustMarshalJSON(types.DefaultGenesis()) } +// ValidateGenesis performs genesis state validation for the IBC interchain acounts module func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { return nil } +// RegisterRESTRoutes implements AppModuleBasic interface func (AppModuleBasic) RegisterRESTRoutes(ctx client.Context, rtr *mux.Router) { - // noop } +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the interchain accounts module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { +} + +// GetTxCmd implements AppModuleBasic interface func (AppModuleBasic) GetTxCmd() *cobra.Command { - // noop return nil } +// GetQueryCmd implements AppModuleBasic interface func (AppModuleBasic) GetQueryCmd() *cobra.Command { - return cli.GetQueryCmd() -} - -// RegisterInterfaces registers module concrete types into protobuf Any. -func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { - types.RegisterInterfaces(registry) -} - -// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the interchain accounts module. -func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + return nil } +// AppModule is the application module for the IBC interchain accounts module type AppModule struct { AppModuleBasic - keeper keeper.Keeper + controllerKeeper *controllerkeeper.Keeper + hostKeeper *hostkeeper.Keeper } -// NewAppModule creates an interchain accounts app module. -func NewAppModule(k keeper.Keeper) AppModule { +// NewAppModule creates a new IBC interchain accounts module +func NewAppModule(controllerKeeper *controllerkeeper.Keeper, hostKeeper *hostkeeper.Keeper) AppModule { return AppModule{ - keeper: k, + controllerKeeper: controllerKeeper, + hostKeeper: hostKeeper, } } +// RegisterInvariants implements the AppModule interface func (AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - // TODO } +// Route implements the AppModule interface func (AppModule) Route() sdk.Route { return sdk.NewRoute(types.RouterKey, nil) } +// NewHandler implements the AppModule interface func (AppModule) NewHandler() sdk.Handler { return nil } +// QuerierRoute implements the AppModule interface func (AppModule) QuerierRoute() string { return types.QuerierRoute } @@ -100,22 +116,44 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd return nil } -// RegisterServices registers a GRPC query service to respond to the -// module-specific GRPC queries. +// RegisterServices registers module services func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterQueryServer(cfg.QueryServer(), am.keeper) } +// InitGenesis performs genesis initialization for the interchain accounts 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) - InitGenesis(ctx, am.keeper, genesisState) + if am.controllerKeeper != nil { + controllerkeeper.InitGenesis(ctx, *am.controllerKeeper, *genesisState.ControllerGenesisState) + } + + if am.hostKeeper != nil { + hostkeeper.InitGenesis(ctx, *am.hostKeeper, *genesisState.HostGenesisState) + } + return []abci.ValidatorUpdate{} } +// ExportGenesis returns the exported genesis state as raw bytes for the interchain accounts module func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - gs := ExportGenesis(ctx, am.keeper) + var ( + controllerGenesisState *types.ControllerGenesisState + hostGenesisState *types.HostGenesisState + ) + + if am.controllerKeeper != nil { + controllerGenesisState = controllerkeeper.ExportGenesis(ctx, *am.controllerKeeper) + } + + if am.hostKeeper != nil { + hostGenesisState = hostkeeper.ExportGenesis(ctx, *am.hostKeeper) + } + + gs := types.NewGenesisState(controllerGenesisState, hostGenesisState) + return cdc.MustMarshalJSON(gs) } @@ -124,7 +162,6 @@ func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock implements the AppModule interface func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - } // EndBlock implements the AppModule interface diff --git a/modules/apps/27-interchain-accounts/types/account.go b/modules/apps/27-interchain-accounts/types/account.go index 87cd0634900..e55c57f3b35 100644 --- a/modules/apps/27-interchain-accounts/types/account.go +++ b/modules/apps/27-interchain-accounts/types/account.go @@ -2,7 +2,6 @@ package types import ( "encoding/json" - "fmt" "strings" crypto "github.com/cosmos/cosmos-sdk/crypto/types" @@ -11,54 +10,34 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" yaml "gopkg.in/yaml.v2" +) - connectiontypes "github.com/cosmos/ibc-go/v2/modules/core/03-connection/types" +var ( + _ authtypes.GenesisAccount = (*InterchainAccount)(nil) + _ InterchainAccountI = (*InterchainAccount)(nil) ) -// GenerateAddress returns an sdk.AccAddress derived using the provided module account address and port identifier. -// The sdk.AccAddress returned is a sub-address of the module account, using the controller chain's port identifier as the derivation key -func GenerateAddress(moduleAccAddr sdk.AccAddress, portID string) sdk.AccAddress { - return sdk.AccAddress(sdkaddress.Derive(moduleAccAddr, []byte(portID))) +// InterchainAccountI wraps the authtypes.AccountI interface +type InterchainAccountI interface { + authtypes.AccountI } -// GeneratePortID generates the portID for a specific owner -// on the controller chain in the format: -// -// 'ics-27---' -// https://github.com/seantking/ibc/tree/sean/ics-27-updates/spec/app/ics-027-interchain-accounts#registering--controlling-flows -// TODO: update link to spec -func GeneratePortID(owner, connectionID, counterpartyConnectionID string) (string, error) { - if strings.TrimSpace(owner) == "" { - return "", sdkerrors.Wrap(ErrInvalidAccountAddress, "owner address cannot be empty") - } - - connectionSeq, err := connectiontypes.ParseConnectionSequence(connectionID) - if err != nil { - return "", sdkerrors.Wrap(err, "invalid connection identifier") - } - - counterpartyConnectionSeq, err := connectiontypes.ParseConnectionSequence(counterpartyConnectionID) - if err != nil { - return "", sdkerrors.Wrap(err, "invalid counterparty connection identifier") - } - - return fmt.Sprint( - VersionPrefix, Delimiter, - connectionSeq, Delimiter, - counterpartyConnectionSeq, Delimiter, - owner, - ), nil +// interchainAccountPretty defines an unexported struct used for encoding the InterchainAccount details +type interchainAccountPretty struct { + Address sdk.AccAddress `json:"address" yaml:"address"` + PubKey string `json:"public_key" yaml:"public_key"` + AccountNumber uint64 `json:"account_number" yaml:"account_number"` + Sequence uint64 `json:"sequence" yaml:"sequence"` + AccountOwner string `json:"account_owner" yaml:"account_owner"` } -type InterchainAccountI interface { - authtypes.AccountI +// GenerateAddress returns an sdk.AccAddress derived using the provided module account address and port identifier. +// The sdk.AccAddress returned is a sub-address of the module account, using the controller chain's port identifier as the derivation key +func GenerateAddress(moduleAccAddr sdk.AccAddress, portID string) sdk.AccAddress { + return sdk.AccAddress(sdkaddress.Derive(moduleAccAddr, []byte(portID))) } -var ( - _ authtypes.GenesisAccount = (*InterchainAccount)(nil) - _ InterchainAccountI = (*InterchainAccount)(nil) -) - +// NewInterchainAccount creates and returns a new InterchainAccount type func NewInterchainAccount(ba *authtypes.BaseAccount, accountOwner string) *InterchainAccount { return &InterchainAccount{ BaseAccount: ba, @@ -66,16 +45,17 @@ func NewInterchainAccount(ba *authtypes.BaseAccount, accountOwner string) *Inter } } -// SetPubKey - Implements AccountI +// SetPubKey implements the authtypes.AccountI interface func (ia InterchainAccount) SetPubKey(pubKey crypto.PubKey) error { return sdkerrors.Wrap(ErrUnsupported, "cannot set public key for interchain account") } -// SetSequence - Implements AccountI +// SetSequence implements the authtypes.AccountI interface func (ia InterchainAccount) SetSequence(seq uint64) error { return sdkerrors.Wrap(ErrUnsupported, "cannot set sequence number for interchain account") } +// Validate implements basic validation of the InterchainAccount func (ia InterchainAccount) Validate() error { if strings.TrimSpace(ia.AccountOwner) == "" { return sdkerrors.Wrap(ErrInvalidAccountAddress, "AccountOwner cannot be empty") @@ -84,20 +64,13 @@ func (ia InterchainAccount) Validate() error { return ia.BaseAccount.Validate() } -type interchainAccountPretty struct { - Address sdk.AccAddress `json:"address" yaml:"address"` - PubKey string `json:"public_key" yaml:"public_key"` - AccountNumber uint64 `json:"account_number" yaml:"account_number"` - Sequence uint64 `json:"sequence" yaml:"sequence"` - AccountOwner string `json:"account_owner" yaml:"account_owner"` -} - +// String returns a string representation of the InterchainAccount func (ia InterchainAccount) String() string { out, _ := ia.MarshalYAML() return string(out) } -// MarshalYAML returns the YAML representation of an InterchainAccount +// MarshalYAML returns the YAML representation of the InterchainAccount func (ia InterchainAccount) MarshalYAML() ([]byte, error) { accAddr, err := sdk.AccAddressFromBech32(ia.Address) if err != nil { @@ -119,7 +92,7 @@ func (ia InterchainAccount) MarshalYAML() ([]byte, error) { return bz, nil } -// MarshalJSON returns the JSON representation of an InterchainAccount. +// MarshalJSON returns the JSON representation of the InterchainAccount func (ia InterchainAccount) MarshalJSON() ([]byte, error) { accAddr, err := sdk.AccAddressFromBech32(ia.Address) if err != nil { @@ -141,7 +114,7 @@ func (ia InterchainAccount) MarshalJSON() ([]byte, error) { return bz, nil } -// UnmarshalJSON unmarshals raw JSON bytes into a ModuleAccount. +// UnmarshalJSON unmarshals raw JSON bytes into the InterchainAccount func (ia *InterchainAccount) UnmarshalJSON(bz []byte) error { var alias interchainAccountPretty if err := json.Unmarshal(bz, &alias); err != nil { diff --git a/modules/apps/27-interchain-accounts/types/account_test.go b/modules/apps/27-interchain-accounts/types/account_test.go index 751bc0f0fa6..b3ad5a37b1f 100644 --- a/modules/apps/27-interchain-accounts/types/account_test.go +++ b/modules/apps/27-interchain-accounts/types/account_test.go @@ -49,80 +49,6 @@ func (suite *TypesTestSuite) TestGenerateAddress() { suite.Require().NotEmpty(accAddr) } -func (suite *TypesTestSuite) TestGeneratePortID() { - var ( - path *ibctesting.Path - owner = TestOwnerAddress - ) - - testCases := []struct { - name string - malleate func() - expValue string - expPass bool - }{ - { - "success", - func() {}, - fmt.Sprint(types.VersionPrefix, types.Delimiter, "0", types.Delimiter, "0", types.Delimiter, TestOwnerAddress), - true, - }, - { - "success with non matching connection sequences", - func() { - path.EndpointA.ConnectionID = "connection-1" - }, - fmt.Sprint(types.VersionPrefix, types.Delimiter, "1", types.Delimiter, "0", types.Delimiter, TestOwnerAddress), - true, - }, - { - "invalid connectionID", - func() { - path.EndpointA.ConnectionID = "connection" - }, - "", - false, - }, - { - "invalid counterparty connectionID", - func() { - path.EndpointB.ConnectionID = "connection" - }, - "", - false, - }, - { - "invalid owner address", - func() { - owner = " " - }, - "", - false, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupTest() // reset - path = ibctesting.NewPath(suite.chainA, suite.chainB) - suite.coordinator.Setup(path) - - tc.malleate() - - portID, err := types.GeneratePortID(owner, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) - - if tc.expPass { - suite.Require().NoError(err, tc.name) - suite.Require().Equal(tc.expValue, portID) - } else { - suite.Require().Error(err, tc.name) - suite.Require().Empty(portID) - } - }) - } -} - func (suite *TypesTestSuite) TestInterchainAccount() { pubkey := secp256k1.GenPrivKey().PubKey() addr := sdk.AccAddress(pubkey.Address()) diff --git a/modules/apps/27-interchain-accounts/types/codec.go b/modules/apps/27-interchain-accounts/types/codec.go index d5cfa419219..971554a83e4 100644 --- a/modules/apps/27-interchain-accounts/types/codec.go +++ b/modules/apps/27-interchain-accounts/types/codec.go @@ -8,6 +8,11 @@ import ( ) var ( + // ModuleCdc references the global interchain accounts module codec. Note, the codec + // should ONLY be used in certain instances of tests and for JSON encoding. + // + // The actual codec used for serialization should be provided to interchain accounts and + // defined at the application level. ModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) ) diff --git a/modules/apps/27-interchain-accounts/types/errors.go b/modules/apps/27-interchain-accounts/types/errors.go index 7b4bfe5d8d1..6061c6ae243 100644 --- a/modules/apps/27-interchain-accounts/types/errors.go +++ b/modules/apps/27-interchain-accounts/types/errors.go @@ -8,7 +8,7 @@ var ( ErrUnknownDataType = sdkerrors.Register(ModuleName, 2, "unknown data type") ErrAccountAlreadyExist = sdkerrors.Register(ModuleName, 3, "account already exist") ErrPortAlreadyBound = sdkerrors.Register(ModuleName, 4, "port is already bound") - ErrUnsupportedChain = sdkerrors.Register(ModuleName, 5, "unsupported chain") + ErrInvalidChannelFlow = sdkerrors.Register(ModuleName, 5, "invalid message sent to channel end") ErrInvalidOutgoingData = sdkerrors.Register(ModuleName, 6, "invalid outgoing data") ErrInvalidRoute = sdkerrors.Register(ModuleName, 7, "invalid route") ErrInterchainAccountNotFound = sdkerrors.Register(ModuleName, 8, "interchain account not found") diff --git a/modules/apps/27-interchain-accounts/types/genesis.go b/modules/apps/27-interchain-accounts/types/genesis.go index f98ce12caa6..757f152a149 100644 --- a/modules/apps/27-interchain-accounts/types/genesis.go +++ b/modules/apps/27-interchain-accounts/types/genesis.go @@ -1,18 +1,47 @@ package types -// DefaultGenesis creates and returns the default interchain accounts GenesisState -// The default GenesisState includes the standard port identifier to which all host chains must bind +// DefaultGenesis creates and returns the interchain accounts GenesisState func DefaultGenesis() *GenesisState { return &GenesisState{ - Ports: []string{PortID}, + ControllerGenesisState: DefaultControllerGenesis(), + HostGenesisState: DefaultHostGenesis(), } } -// NewGenesisState creates a returns a new GenesisState instance -func NewGenesisState(ports []string, channels []*ActiveChannel, accounts []*RegisteredInterchainAccount) *GenesisState { +// NewGenesisState creates and returns a new GenesisState instance from the provided controller and host genesis state types +func NewGenesisState(controllerGenesisState *ControllerGenesisState, hostGenesisState *HostGenesisState) *GenesisState { return &GenesisState{ + ControllerGenesisState: controllerGenesisState, + HostGenesisState: hostGenesisState, + } +} + +// DefaultControllerGenesis creates and returns the default interchain accounts ControllerGenesisState +func DefaultControllerGenesis() *ControllerGenesisState { + return &ControllerGenesisState{} +} + +// NewControllerGenesisState creates a returns a new ControllerGenesisState instance +func NewControllerGenesisState(channels []*ActiveChannel, accounts []*RegisteredInterchainAccount, ports []string) *ControllerGenesisState { + return &ControllerGenesisState{ ActiveChannels: channels, InterchainAccounts: accounts, Ports: ports, } } + +// DefaultHostGenesis creates and returns the default interchain accounts HostGenesisState +func DefaultHostGenesis() *HostGenesisState { + return &HostGenesisState{ + Port: PortID, + } +} + +// NewHostGenesisState creates a returns a new HostGenesisState instance +func NewHostGenesisState(channels []*ActiveChannel, accounts []*RegisteredInterchainAccount, port string) *HostGenesisState { + return &HostGenesisState{ + ActiveChannels: channels, + InterchainAccounts: accounts, + Port: port, + } +} diff --git a/modules/apps/27-interchain-accounts/types/genesis.pb.go b/modules/apps/27-interchain-accounts/types/genesis.pb.go index 32d38337ac1..3faa19c0d88 100644 --- a/modules/apps/27-interchain-accounts/types/genesis.pb.go +++ b/modules/apps/27-interchain-accounts/types/genesis.pb.go @@ -25,9 +25,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines the interchain accounts genesis state type GenesisState struct { - ActiveChannels []*ActiveChannel `protobuf:"bytes,1,rep,name=active_channels,json=activeChannels,proto3" json:"active_channels,omitempty" yaml:"active_channels"` - InterchainAccounts []*RegisteredInterchainAccount `protobuf:"bytes,2,rep,name=interchain_accounts,json=interchainAccounts,proto3" json:"interchain_accounts,omitempty" yaml:"interchain_accounts"` - Ports []string `protobuf:"bytes,3,rep,name=ports,proto3" json:"ports,omitempty"` + ControllerGenesisState *ControllerGenesisState `protobuf:"bytes,1,opt,name=controller_genesis_state,json=controllerGenesisState,proto3" json:"controller_genesis_state,omitempty" yaml:"controller_genesis_state"` + HostGenesisState *HostGenesisState `protobuf:"bytes,2,opt,name=host_genesis_state,json=hostGenesisState,proto3" json:"host_genesis_state,omitempty" yaml:"host_genesis_state"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -63,27 +62,142 @@ func (m *GenesisState) XXX_DiscardUnknown() { var xxx_messageInfo_GenesisState proto.InternalMessageInfo -func (m *GenesisState) GetActiveChannels() []*ActiveChannel { +func (m *GenesisState) GetControllerGenesisState() *ControllerGenesisState { + if m != nil { + return m.ControllerGenesisState + } + return nil +} + +func (m *GenesisState) GetHostGenesisState() *HostGenesisState { + if m != nil { + return m.HostGenesisState + } + return nil +} + +// ControllerGenesisState defines the interchain accounts controller genesis state +type ControllerGenesisState struct { + ActiveChannels []*ActiveChannel `protobuf:"bytes,1,rep,name=active_channels,json=activeChannels,proto3" json:"active_channels,omitempty" yaml:"active_channels"` + InterchainAccounts []*RegisteredInterchainAccount `protobuf:"bytes,2,rep,name=interchain_accounts,json=interchainAccounts,proto3" json:"interchain_accounts,omitempty" yaml:"interchain_accounts"` + Ports []string `protobuf:"bytes,3,rep,name=ports,proto3" json:"ports,omitempty"` +} + +func (m *ControllerGenesisState) Reset() { *m = ControllerGenesisState{} } +func (m *ControllerGenesisState) String() string { return proto.CompactTextString(m) } +func (*ControllerGenesisState) ProtoMessage() {} +func (*ControllerGenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_629b3ced0911516b, []int{1} +} +func (m *ControllerGenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ControllerGenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ControllerGenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ControllerGenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ControllerGenesisState.Merge(m, src) +} +func (m *ControllerGenesisState) XXX_Size() int { + return m.Size() +} +func (m *ControllerGenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_ControllerGenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_ControllerGenesisState proto.InternalMessageInfo + +func (m *ControllerGenesisState) GetActiveChannels() []*ActiveChannel { if m != nil { return m.ActiveChannels } return nil } -func (m *GenesisState) GetInterchainAccounts() []*RegisteredInterchainAccount { +func (m *ControllerGenesisState) GetInterchainAccounts() []*RegisteredInterchainAccount { if m != nil { return m.InterchainAccounts } return nil } -func (m *GenesisState) GetPorts() []string { +func (m *ControllerGenesisState) GetPorts() []string { if m != nil { return m.Ports } return nil } +// HostGenesisState defines the interchain accounts host genesis state +type HostGenesisState struct { + ActiveChannels []*ActiveChannel `protobuf:"bytes,1,rep,name=active_channels,json=activeChannels,proto3" json:"active_channels,omitempty" yaml:"active_channels"` + InterchainAccounts []*RegisteredInterchainAccount `protobuf:"bytes,2,rep,name=interchain_accounts,json=interchainAccounts,proto3" json:"interchain_accounts,omitempty" yaml:"interchain_accounts"` + Port string `protobuf:"bytes,3,opt,name=port,proto3" json:"port,omitempty"` +} + +func (m *HostGenesisState) Reset() { *m = HostGenesisState{} } +func (m *HostGenesisState) String() string { return proto.CompactTextString(m) } +func (*HostGenesisState) ProtoMessage() {} +func (*HostGenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_629b3ced0911516b, []int{2} +} +func (m *HostGenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HostGenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HostGenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *HostGenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_HostGenesisState.Merge(m, src) +} +func (m *HostGenesisState) XXX_Size() int { + return m.Size() +} +func (m *HostGenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_HostGenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_HostGenesisState proto.InternalMessageInfo + +func (m *HostGenesisState) GetActiveChannels() []*ActiveChannel { + if m != nil { + return m.ActiveChannels + } + return nil +} + +func (m *HostGenesisState) GetInterchainAccounts() []*RegisteredInterchainAccount { + if m != nil { + return m.InterchainAccounts + } + return nil +} + +func (m *HostGenesisState) GetPort() string { + if m != nil { + return m.Port + } + return "" +} + // ActiveChannel contains a pairing of port ID and channel ID for an active interchain accounts channel type ActiveChannel struct { PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty" yaml:"port_id"` @@ -94,7 +208,7 @@ func (m *ActiveChannel) Reset() { *m = ActiveChannel{} } func (m *ActiveChannel) String() string { return proto.CompactTextString(m) } func (*ActiveChannel) ProtoMessage() {} func (*ActiveChannel) Descriptor() ([]byte, []int) { - return fileDescriptor_629b3ced0911516b, []int{1} + return fileDescriptor_629b3ced0911516b, []int{3} } func (m *ActiveChannel) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -147,7 +261,7 @@ func (m *RegisteredInterchainAccount) Reset() { *m = RegisteredInterchai func (m *RegisteredInterchainAccount) String() string { return proto.CompactTextString(m) } func (*RegisteredInterchainAccount) ProtoMessage() {} func (*RegisteredInterchainAccount) Descriptor() ([]byte, []int) { - return fileDescriptor_629b3ced0911516b, []int{2} + return fileDescriptor_629b3ced0911516b, []int{4} } func (m *RegisteredInterchainAccount) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -192,6 +306,8 @@ func (m *RegisteredInterchainAccount) GetAccountAddress() string { func init() { proto.RegisterType((*GenesisState)(nil), "ibc.applications.interchain_accounts.v1.GenesisState") + proto.RegisterType((*ControllerGenesisState)(nil), "ibc.applications.interchain_accounts.v1.ControllerGenesisState") + proto.RegisterType((*HostGenesisState)(nil), "ibc.applications.interchain_accounts.v1.HostGenesisState") proto.RegisterType((*ActiveChannel)(nil), "ibc.applications.interchain_accounts.v1.ActiveChannel") proto.RegisterType((*RegisteredInterchainAccount)(nil), "ibc.applications.interchain_accounts.v1.RegisteredInterchainAccount") } @@ -201,34 +317,41 @@ func init() { } var fileDescriptor_629b3ced0911516b = []byte{ - // 426 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x4f, 0x8b, 0x13, 0x31, - 0x18, 0xc6, 0x9b, 0x16, 0x57, 0x1a, 0x75, 0xc5, 0xb8, 0x4a, 0xa9, 0x30, 0x2d, 0xb9, 0x58, 0x90, - 0x4e, 0xd8, 0xfa, 0x0f, 0xbc, 0xb5, 0xab, 0x48, 0xaf, 0xe3, 0xcd, 0xcb, 0x90, 0xc9, 0x84, 0x69, - 0xa0, 0x33, 0x19, 0xe6, 0x4d, 0x07, 0x16, 0x3f, 0x84, 0x5e, 0xfc, 0x30, 0x7e, 0x03, 0x8f, 0x7b, - 0xf4, 0x34, 0x48, 0xfb, 0x0d, 0xe6, 0x13, 0xc8, 0x24, 0xc3, 0xee, 0x76, 0x29, 0xb2, 0x7b, 0x4b, - 0xf2, 0x3c, 0xbf, 0x37, 0x4f, 0xf2, 0xbe, 0xf8, 0xad, 0x8a, 0x04, 0xe3, 0x79, 0xbe, 0x56, 0x82, - 0x1b, 0xa5, 0x33, 0x60, 0x2a, 0x33, 0xb2, 0x10, 0x2b, 0xae, 0xb2, 0x90, 0x0b, 0xa1, 0x37, 0x99, - 0x01, 0x56, 0x9e, 0xb2, 0x44, 0x66, 0x12, 0x14, 0xf8, 0x79, 0xa1, 0x8d, 0x26, 0x2f, 0x55, 0x24, - 0xfc, 0xeb, 0x98, 0x7f, 0x00, 0xf3, 0xcb, 0xd3, 0xe1, 0x49, 0xa2, 0x13, 0x6d, 0x19, 0xd6, 0xac, - 0x1c, 0x4e, 0x7f, 0x75, 0xf1, 0xc3, 0xcf, 0xae, 0xe0, 0x17, 0xc3, 0x8d, 0x24, 0xdf, 0xf0, 0x63, - 0x2e, 0x8c, 0x2a, 0x65, 0x28, 0x56, 0x3c, 0xcb, 0xe4, 0x1a, 0x06, 0x68, 0xdc, 0x9b, 0x3c, 0x98, - 0xbd, 0xf3, 0x6f, 0x79, 0x93, 0x3f, 0xb7, 0xfc, 0x99, 0xc3, 0x17, 0xc3, 0xba, 0x1a, 0x3d, 0x3f, - 0xe7, 0xe9, 0xfa, 0x03, 0xbd, 0x51, 0x98, 0x06, 0xc7, 0xfc, 0xba, 0x15, 0xc8, 0x4f, 0x84, 0x9f, - 0x1e, 0x28, 0x3a, 0xe8, 0xda, 0x04, 0x1f, 0x6f, 0x9d, 0x20, 0x90, 0x89, 0x02, 0x23, 0x0b, 0x19, - 0x2f, 0x2f, 0x0d, 0x73, 0xa7, 0x2f, 0xbc, 0xba, 0x1a, 0x0d, 0x5d, 0x9e, 0x03, 0x34, 0x0d, 0x88, - 0xba, 0x89, 0x00, 0x39, 0xc1, 0xf7, 0x72, 0x5d, 0x18, 0x18, 0xf4, 0xc6, 0xbd, 0x49, 0x3f, 0x70, - 0x1b, 0x5a, 0xe0, 0x47, 0x7b, 0x4f, 0x25, 0xaf, 0xf0, 0xfd, 0x46, 0x09, 0x55, 0x3c, 0x40, 0x63, - 0x34, 0xe9, 0x2f, 0x48, 0x5d, 0x8d, 0x8e, 0xdd, 0x5d, 0xad, 0x40, 0x83, 0xa3, 0x66, 0xb5, 0x8c, - 0xc9, 0x1b, 0x8c, 0xdb, 0x8f, 0x68, 0xfc, 0x5d, 0xeb, 0x7f, 0x56, 0x57, 0xa3, 0x27, 0xce, 0x7f, - 0xa5, 0xd1, 0xa0, 0xdf, 0x6e, 0x96, 0x31, 0xfd, 0x8e, 0xf0, 0x8b, 0xff, 0xbc, 0xee, 0x6e, 0x11, - 0xce, 0x9a, 0x5e, 0x5b, 0x2e, 0xe4, 0x71, 0x5c, 0x48, 0x80, 0x36, 0xc7, 0x5e, 0xcf, 0xf6, 0x0c, - 0xb6, 0x67, 0xf6, 0x64, 0xee, 0x0e, 0x16, 0xe1, 0xef, 0xad, 0x87, 0x2e, 0xb6, 0x1e, 0xfa, 0xbb, - 0xf5, 0xd0, 0x8f, 0x9d, 0xd7, 0xb9, 0xd8, 0x79, 0x9d, 0x3f, 0x3b, 0xaf, 0xf3, 0xf5, 0x53, 0xa2, - 0xcc, 0x6a, 0x13, 0xf9, 0x42, 0xa7, 0x4c, 0x68, 0x48, 0x35, 0x30, 0x15, 0x89, 0x69, 0xa2, 0x59, - 0x39, 0x63, 0xa9, 0x8e, 0x37, 0x6b, 0x09, 0xcd, 0xc4, 0x03, 0x9b, 0xbd, 0x9f, 0x5e, 0xfd, 0xfa, - 0xf4, 0x72, 0xd8, 0xcd, 0x79, 0x2e, 0x21, 0x3a, 0xb2, 0x93, 0xfa, 0xfa, 0x5f, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xc5, 0x32, 0x06, 0x92, 0x21, 0x03, 0x00, 0x00, + // 540 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x94, 0xbf, 0x8f, 0xd3, 0x30, + 0x14, 0xc7, 0xeb, 0x14, 0x0e, 0xd5, 0x07, 0xc7, 0x61, 0x8e, 0x2a, 0x14, 0x91, 0x54, 0x66, 0xa0, + 0x12, 0x6a, 0xa2, 0x2b, 0xbf, 0x04, 0x0b, 0x6a, 0x0b, 0x82, 0xae, 0x61, 0x63, 0x89, 0x52, 0xc7, + 0x4a, 0x2d, 0xa5, 0x71, 0x15, 0xbb, 0x95, 0x4e, 0xec, 0xac, 0xb0, 0xb0, 0xf2, 0x77, 0x20, 0x31, + 0xb2, 0x30, 0xde, 0xc8, 0x14, 0xa1, 0xf6, 0x3f, 0xc8, 0x5f, 0x80, 0x62, 0x47, 0xa5, 0x2d, 0x01, + 0x95, 0x9d, 0xcd, 0xf6, 0x7b, 0xdf, 0xf7, 0x3e, 0xef, 0x3d, 0xf9, 0xc1, 0x87, 0x6c, 0x4c, 0xdc, + 0x60, 0x36, 0x8b, 0x19, 0x09, 0x24, 0xe3, 0x89, 0x70, 0x59, 0x22, 0x69, 0x4a, 0x26, 0x01, 0x4b, + 0xfc, 0x80, 0x10, 0x3e, 0x4f, 0xa4, 0x70, 0x17, 0xa7, 0x6e, 0x44, 0x13, 0x2a, 0x98, 0x70, 0x66, + 0x29, 0x97, 0x1c, 0xdd, 0x65, 0x63, 0xe2, 0x6c, 0xca, 0x9c, 0x0a, 0x99, 0xb3, 0x38, 0x6d, 0x9d, + 0x44, 0x3c, 0xe2, 0x4a, 0xe3, 0x16, 0x27, 0x2d, 0xc7, 0x9f, 0x0d, 0x78, 0xf9, 0xa5, 0x0e, 0xf8, + 0x5a, 0x06, 0x92, 0xa2, 0x4f, 0x00, 0x9a, 0x84, 0x27, 0x32, 0xe5, 0x71, 0x4c, 0x53, 0xbf, 0x4c, + 0xe6, 0x8b, 0xc2, 0x68, 0x82, 0x36, 0xe8, 0x1c, 0xf6, 0x9e, 0x39, 0x7b, 0xe6, 0x74, 0x86, 0xeb, + 0x40, 0x9b, 0x39, 0x06, 0x77, 0xf2, 0xcc, 0xb6, 0xcf, 0x82, 0x69, 0xfc, 0x14, 0xff, 0x29, 0x15, + 0xf6, 0x9a, 0xa4, 0x52, 0x8c, 0xde, 0x01, 0x88, 0x26, 0x5c, 0xc8, 0x1d, 0x34, 0x43, 0xa1, 0x3d, + 0xd9, 0x1b, 0xed, 0x15, 0x17, 0x72, 0x0b, 0xea, 0x76, 0x9e, 0xd9, 0x37, 0x35, 0xd4, 0xef, 0xe1, + 0xb1, 0x77, 0x3c, 0xd9, 0x11, 0xe0, 0xaf, 0x06, 0x6c, 0x56, 0x17, 0x88, 0xde, 0xc2, 0xab, 0x01, + 0x91, 0x6c, 0x41, 0x7d, 0x32, 0x09, 0x92, 0x84, 0xc6, 0xc2, 0x04, 0xed, 0x7a, 0xe7, 0xb0, 0xf7, + 0x68, 0x6f, 0xbe, 0xbe, 0xd2, 0x0f, 0xb5, 0x7c, 0xd0, 0xca, 0x33, 0xbb, 0xa9, 0xe1, 0x76, 0x02, + 0x63, 0xef, 0x28, 0xd8, 0x74, 0x15, 0xe8, 0x23, 0x80, 0xd7, 0x2b, 0x82, 0x9a, 0x86, 0x22, 0x78, + 0xbe, 0x37, 0x81, 0x47, 0x23, 0x26, 0x24, 0x4d, 0x69, 0x38, 0x5a, 0x3b, 0xf4, 0xb5, 0x7d, 0x60, + 0xe5, 0x99, 0xdd, 0xd2, 0x3c, 0x15, 0x6a, 0xec, 0x21, 0xb6, 0x2b, 0x11, 0xe8, 0x04, 0x5e, 0x9c, + 0xf1, 0x54, 0x0a, 0xb3, 0xde, 0xae, 0x77, 0x1a, 0x9e, 0xbe, 0xe0, 0x2f, 0x06, 0x3c, 0xde, 0x9d, + 0xc5, 0xff, 0xfe, 0x55, 0xf5, 0x0f, 0xc1, 0x0b, 0x45, 0xcb, 0xcc, 0x7a, 0x1b, 0x74, 0x1a, 0x9e, + 0x3a, 0xe3, 0x14, 0x5e, 0xd9, 0x2a, 0x14, 0xdd, 0x83, 0x97, 0x0a, 0x83, 0xcf, 0x42, 0xf5, 0x59, + 0x1b, 0x03, 0x94, 0x67, 0xf6, 0x91, 0xce, 0x54, 0x1a, 0xb0, 0x77, 0x50, 0x9c, 0x46, 0x21, 0x7a, + 0x00, 0x61, 0xd9, 0x86, 0xc2, 0xdf, 0x50, 0xfe, 0x37, 0xf2, 0xcc, 0xbe, 0x56, 0xfe, 0xcd, 0xb5, + 0x0d, 0x7b, 0x8d, 0xf2, 0x32, 0x0a, 0xf1, 0x7b, 0x00, 0x6f, 0xfd, 0xa5, 0xb6, 0x7f, 0x43, 0x18, + 0x16, 0x93, 0x56, 0x3a, 0x3f, 0x08, 0xc3, 0x94, 0x0a, 0x51, 0x72, 0x6c, 0x4d, 0x6c, 0xcb, 0x41, + 0x4d, 0x4c, 0xbd, 0xf4, 0xf5, 0xc3, 0xc0, 0xff, 0xb6, 0xb4, 0xc0, 0xf9, 0xd2, 0x02, 0x3f, 0x96, + 0x16, 0xf8, 0xb0, 0xb2, 0x6a, 0xe7, 0x2b, 0xab, 0xf6, 0x7d, 0x65, 0xd5, 0xde, 0xbc, 0x88, 0x98, + 0x9c, 0xcc, 0xc7, 0x0e, 0xe1, 0x53, 0x97, 0x70, 0x31, 0xe5, 0xc2, 0x65, 0x63, 0xd2, 0x8d, 0xb8, + 0xbb, 0xe8, 0xb9, 0x53, 0x1e, 0xce, 0x63, 0x2a, 0x8a, 0xa5, 0x2b, 0xdc, 0xde, 0xe3, 0xee, 0xaf, + 0x9e, 0x77, 0xd7, 0xfb, 0x56, 0x9e, 0xcd, 0xa8, 0x18, 0x1f, 0xa8, 0x65, 0x79, 0xff, 0x67, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x21, 0x52, 0x9a, 0x46, 0xa4, 0x05, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -247,6 +370,53 @@ func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { } func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.HostGenesisState != nil { + { + size, err := m.HostGenesisState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.ControllerGenesisState != nil { + { + size, err := m.ControllerGenesisState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ControllerGenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControllerGenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ControllerGenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -291,6 +461,64 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *HostGenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *HostGenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HostGenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Port) > 0 { + i -= len(m.Port) + copy(dAtA[i:], m.Port) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Port))) + i-- + dAtA[i] = 0x1a + } + if len(m.InterchainAccounts) > 0 { + for iNdEx := len(m.InterchainAccounts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.InterchainAccounts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.ActiveChannels) > 0 { + for iNdEx := len(m.ActiveChannels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ActiveChannels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func (m *ActiveChannel) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -377,6 +605,23 @@ func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { return base } func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ControllerGenesisState != nil { + l = m.ControllerGenesisState.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + if m.HostGenesisState != nil { + l = m.HostGenesisState.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func (m *ControllerGenesisState) Size() (n int) { if m == nil { return 0 } @@ -403,6 +648,31 @@ func (m *GenesisState) Size() (n int) { return n } +func (m *HostGenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ActiveChannels) > 0 { + for _, e := range m.ActiveChannels { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.InterchainAccounts) > 0 { + for _, e := range m.InterchainAccounts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = len(m.Port) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + func (m *ActiveChannel) Size() (n int) { if m == nil { return 0 @@ -472,6 +742,128 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ControllerGenesisState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ControllerGenesisState == nil { + m.ControllerGenesisState = &ControllerGenesisState{} + } + if err := m.ControllerGenesisState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HostGenesisState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.HostGenesisState == nil { + m.HostGenesisState = &HostGenesisState{} + } + if err := m.HostGenesisState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ControllerGenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControllerGenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControllerGenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ActiveChannels", wireType) @@ -593,6 +985,156 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { } return nil } +func (m *HostGenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HostGenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HostGenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActiveChannels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ActiveChannels = append(m.ActiveChannels, &ActiveChannel{}) + if err := m.ActiveChannels[len(m.ActiveChannels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InterchainAccounts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InterchainAccounts = append(m.InterchainAccounts, &RegisteredInterchainAccount{}) + if err := m.InterchainAccounts[len(m.InterchainAccounts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Port = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ActiveChannel) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/modules/apps/27-interchain-accounts/types/keys.go b/modules/apps/27-interchain-accounts/types/keys.go index a71f6c0c32c..d4df0db4c93 100644 --- a/modules/apps/27-interchain-accounts/types/keys.go +++ b/modules/apps/27-interchain-accounts/types/keys.go @@ -2,12 +2,6 @@ package types import ( "fmt" - "strconv" - "strings" - - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - porttypes "github.com/cosmos/ibc-go/v2/modules/core/05-port/types" ) const ( @@ -31,10 +25,6 @@ const ( // Delimiter is the delimiter used for the interchain accounts version string Delimiter = "." - - // ControllerPortFormat is the expected port identifier format to which controller chains must conform - // See (TODO: Link to spec when updated) - ControllerPortFormat = "..." ) var ( @@ -48,11 +38,6 @@ var ( PortKeyPrefix = "port" ) -// NewVersion returns a complete version string in the format: VersionPrefix + Delimter + AccAddress -func NewAppVersion(versionPrefix, accAddr string) string { - return fmt.Sprint(versionPrefix, Delimiter, accAddr) -} - // KeyActiveChannel creates and returns a new key used for active channels store operations func KeyActiveChannel(portID string) []byte { return []byte(fmt.Sprintf("%s/%s", ActiveChannelKeyPrefix, portID)) @@ -67,45 +52,3 @@ func KeyOwnerAccount(portID string) []byte { func KeyPort(portID string) []byte { return []byte(fmt.Sprintf("%s/%s", PortKeyPrefix, portID)) } - -// ParseControllerConnSequence attempts to parse the controller connection sequence from the provided port identifier -// The port identifier must match the controller chain format outlined in (TODO: link spec), otherwise an empty string is returned -func ParseControllerConnSequence(portID string) (uint64, error) { - s := strings.Split(portID, Delimiter) - if len(s) != 4 { - return 0, sdkerrors.Wrap(porttypes.ErrInvalidPort, "failed to parse port identifier") - } - - seq, err := strconv.ParseUint(s[1], 10, 64) - if err != nil { - return 0, sdkerrors.Wrapf(err, "failed to parse connection sequence (%s)", s[1]) - } - - return seq, nil -} - -// ParseHostConnSequence attempts to parse the host connection sequence from the provided port identifier -// The port identifier must match the controller chain format outlined in (TODO: link spec), otherwise an empty string is returned -func ParseHostConnSequence(portID string) (uint64, error) { - s := strings.Split(portID, Delimiter) - if len(s) != 4 { - return 0, sdkerrors.Wrap(porttypes.ErrInvalidPort, "failed to parse port identifier") - } - - seq, err := strconv.ParseUint(s[2], 10, 64) - if err != nil { - return 0, sdkerrors.Wrapf(err, "failed to parse connection sequence (%s)", s[2]) - } - - return seq, nil -} - -// ParseAddressFromVersion attempts to extract the associated account address from the provided version string -func ParseAddressFromVersion(version string) (string, error) { - s := strings.Split(version, Delimiter) - if len(s) != 2 { - return "", sdkerrors.Wrap(ErrInvalidVersion, "failed to parse version") - } - - return s[1], nil -} diff --git a/modules/apps/27-interchain-accounts/types/keys_test.go b/modules/apps/27-interchain-accounts/types/keys_test.go index 0bb4c7cea84..037061a3d3e 100644 --- a/modules/apps/27-interchain-accounts/types/keys_test.go +++ b/modules/apps/27-interchain-accounts/types/keys_test.go @@ -1,8 +1,6 @@ package types_test import ( - "fmt" - "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" ) @@ -15,132 +13,3 @@ func (suite *TypesTestSuite) TestKeyOwnerAccount() { key := types.KeyOwnerAccount("port-id") suite.Require().Equal("owner/port-id", string(key)) } - -func (suite *TypesTestSuite) TestParseControllerConnSequence() { - - testCases := []struct { - name string - portID string - expValue uint64 - expPass bool - }{ - { - "success", - TestPortID, - 0, - true, - }, - { - "failed to parse port identifier", - "invalid-port-id", - 0, - false, - }, - { - "failed to parse connection sequence", - "ics27-1.x.y.cosmos1", - 0, - false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - connSeq, err := types.ParseControllerConnSequence(tc.portID) - - if tc.expPass { - suite.Require().Equal(tc.expValue, connSeq) - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Zero(connSeq) - suite.Require().Error(err, tc.name) - } - }) - } -} - -func (suite *TypesTestSuite) TestParseHostConnSequence() { - - testCases := []struct { - name string - portID string - expValue uint64 - expPass bool - }{ - { - "success", - TestPortID, - 0, - true, - }, - { - "failed to parse port identifier", - "invalid-port-id", - 0, - false, - }, - { - "failed to parse connection sequence", - "ics27-1.x.y.cosmos1", - 0, - false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - connSeq, err := types.ParseHostConnSequence(tc.portID) - - if tc.expPass { - suite.Require().Equal(tc.expValue, connSeq) - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Zero(connSeq) - suite.Require().Error(err, tc.name) - } - }) - } -} - -func (suite *TypesTestSuite) TestParseAddressFromVersion() { - - testCases := []struct { - name string - version string - expValue string - expPass bool - }{ - { - "success", - types.NewAppVersion(types.VersionPrefix, TestOwnerAddress), - TestOwnerAddress, - true, - }, - { - "failed to parse address from version", - "invalid-version-string", - "", - false, - }, - { - "failure with multiple delimiters", - fmt.Sprint(types.NewAppVersion(types.VersionPrefix, TestOwnerAddress), types.Delimiter, types.NewAppVersion(types.VersionPrefix, TestOwnerAddress)), - "", - false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - addr, err := types.ParseAddressFromVersion(tc.version) - - if tc.expPass { - suite.Require().Equal(tc.expValue, addr) - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Empty(addr) - suite.Require().Error(err, tc.name) - } - }) - } -} diff --git a/modules/apps/27-interchain-accounts/types/packet.go b/modules/apps/27-interchain-accounts/types/packet.go index 3c5223f4390..e7669a77fc9 100644 --- a/modules/apps/27-interchain-accounts/types/packet.go +++ b/modules/apps/27-interchain-accounts/types/packet.go @@ -6,6 +6,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) +// MaxMemoCharLength defines the maximum length for the InterchainAccountPacketData memo field const MaxMemoCharLength = 256 // ValidateBasic performs basic validation of the interchain account packet data. diff --git a/modules/apps/27-interchain-accounts/types/port.go b/modules/apps/27-interchain-accounts/types/port.go new file mode 100644 index 00000000000..c44c5eda9ba --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/port.go @@ -0,0 +1,78 @@ +package types + +import ( + "fmt" + "strconv" + "strings" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + connectiontypes "github.com/cosmos/ibc-go/v2/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v2/modules/core/05-port/types" +) + +const ( + // ControllerPortFormat is the expected port identifier format to which controller chains must conform + // See (TODO: Link to spec when updated) + ControllerPortFormat = "..." +) + +// GeneratePortID generates an interchain accounts controller port identifier for the provided owner +// in the following format: +// +// 'ics-27---' +// https://github.com/seantking/ibc/tree/sean/ics-27-updates/spec/app/ics-027-interchain-accounts#registering--controlling-flows +// TODO: update link to spec +func GeneratePortID(owner, connectionID, counterpartyConnectionID string) (string, error) { + if strings.TrimSpace(owner) == "" { + return "", sdkerrors.Wrap(ErrInvalidAccountAddress, "owner address cannot be empty") + } + + connectionSeq, err := connectiontypes.ParseConnectionSequence(connectionID) + if err != nil { + return "", sdkerrors.Wrap(err, "invalid connection identifier") + } + + counterpartyConnectionSeq, err := connectiontypes.ParseConnectionSequence(counterpartyConnectionID) + if err != nil { + return "", sdkerrors.Wrap(err, "invalid counterparty connection identifier") + } + + return fmt.Sprint( + VersionPrefix, Delimiter, + connectionSeq, Delimiter, + counterpartyConnectionSeq, Delimiter, + owner, + ), nil +} + +// ParseControllerConnSequence attempts to parse the controller connection sequence from the provided port identifier +// The port identifier must match the controller chain format outlined in (TODO: link spec), otherwise an empty string is returned +func ParseControllerConnSequence(portID string) (uint64, error) { + s := strings.Split(portID, Delimiter) + if len(s) != 4 { + return 0, sdkerrors.Wrap(porttypes.ErrInvalidPort, "failed to parse port identifier") + } + + seq, err := strconv.ParseUint(s[1], 10, 64) + if err != nil { + return 0, sdkerrors.Wrapf(err, "failed to parse connection sequence (%s)", s[1]) + } + + return seq, nil +} + +// ParseHostConnSequence attempts to parse the host connection sequence from the provided port identifier +// The port identifier must match the controller chain format outlined in (TODO: link spec), otherwise an empty string is returned +func ParseHostConnSequence(portID string) (uint64, error) { + s := strings.Split(portID, Delimiter) + if len(s) != 4 { + return 0, sdkerrors.Wrap(porttypes.ErrInvalidPort, "failed to parse port identifier") + } + + seq, err := strconv.ParseUint(s[2], 10, 64) + if err != nil { + return 0, sdkerrors.Wrapf(err, "failed to parse connection sequence (%s)", s[2]) + } + + return seq, nil +} diff --git a/modules/apps/27-interchain-accounts/types/port_test.go b/modules/apps/27-interchain-accounts/types/port_test.go new file mode 100644 index 00000000000..cd12548c8d5 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/port_test.go @@ -0,0 +1,169 @@ +package types_test + +import ( + "fmt" + + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v2/testing" +) + +func (suite *TypesTestSuite) TestGeneratePortID() { + var ( + path *ibctesting.Path + owner = TestOwnerAddress + ) + + testCases := []struct { + name string + malleate func() + expValue string + expPass bool + }{ + { + "success", + func() {}, + fmt.Sprint(types.VersionPrefix, types.Delimiter, "0", types.Delimiter, "0", types.Delimiter, TestOwnerAddress), + true, + }, + { + "success with non matching connection sequences", + func() { + path.EndpointA.ConnectionID = "connection-1" + }, + fmt.Sprint(types.VersionPrefix, types.Delimiter, "1", types.Delimiter, "0", types.Delimiter, TestOwnerAddress), + true, + }, + { + "invalid connectionID", + func() { + path.EndpointA.ConnectionID = "connection" + }, + "", + false, + }, + { + "invalid counterparty connectionID", + func() { + path.EndpointB.ConnectionID = "connection" + }, + "", + false, + }, + { + "invalid owner address", + func() { + owner = " " + }, + "", + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + + tc.malleate() // malleate mutates test data + + portID, err := types.GeneratePortID(owner, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) + + if tc.expPass { + suite.Require().NoError(err, tc.name) + suite.Require().Equal(tc.expValue, portID) + } else { + suite.Require().Error(err, tc.name) + suite.Require().Empty(portID) + } + }) + } +} + +func (suite *TypesTestSuite) TestParseControllerConnSequence() { + + testCases := []struct { + name string + portID string + expValue uint64 + expPass bool + }{ + { + "success", + TestPortID, + 0, + true, + }, + { + "failed to parse port identifier", + "invalid-port-id", + 0, + false, + }, + { + "failed to parse connection sequence", + "ics27-1.x.y.cosmos1", + 0, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + connSeq, err := types.ParseControllerConnSequence(tc.portID) + + if tc.expPass { + suite.Require().Equal(tc.expValue, connSeq) + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Zero(connSeq) + suite.Require().Error(err, tc.name) + } + }) + } +} + +func (suite *TypesTestSuite) TestParseHostConnSequence() { + + testCases := []struct { + name string + portID string + expValue uint64 + expPass bool + }{ + { + "success", + TestPortID, + 0, + true, + }, + { + "failed to parse port identifier", + "invalid-port-id", + 0, + false, + }, + { + "failed to parse connection sequence", + "ics27-1.x.y.cosmos1", + 0, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + connSeq, err := types.ParseHostConnSequence(tc.portID) + + if tc.expPass { + suite.Require().Equal(tc.expValue, connSeq) + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Zero(connSeq) + suite.Require().Error(err, tc.name) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/types/query.pb.go b/modules/apps/27-interchain-accounts/types/query.pb.go deleted file mode 100644 index 29557f53dca..00000000000 --- a/modules/apps/27-interchain-accounts/types/query.pb.go +++ /dev/null @@ -1,584 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: ibc/applications/interchain_accounts/v1/query.proto - -package types - -import ( - context "context" - fmt "fmt" - _ "github.com/gogo/protobuf/gogoproto" - grpc1 "github.com/gogo/protobuf/grpc" - proto "github.com/gogo/protobuf/proto" - _ "google.golang.org/genproto/googleapis/api/annotations" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - io "io" - math "math" - math_bits "math/bits" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// Query request for an interchain account address -type QueryInterchainAccountAddressRequest struct { - // Counterparty PortID is the portID on the controller chain - CounterpartyPortId string `protobuf:"bytes,1,opt,name=counterparty_port_id,json=counterpartyPortId,proto3" json:"counterparty_port_id,omitempty"` -} - -func (m *QueryInterchainAccountAddressRequest) Reset() { *m = QueryInterchainAccountAddressRequest{} } -func (m *QueryInterchainAccountAddressRequest) String() string { return proto.CompactTextString(m) } -func (*QueryInterchainAccountAddressRequest) ProtoMessage() {} -func (*QueryInterchainAccountAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_72a16b57c3343764, []int{0} -} -func (m *QueryInterchainAccountAddressRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryInterchainAccountAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryInterchainAccountAddressRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *QueryInterchainAccountAddressRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryInterchainAccountAddressRequest.Merge(m, src) -} -func (m *QueryInterchainAccountAddressRequest) XXX_Size() int { - return m.Size() -} -func (m *QueryInterchainAccountAddressRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryInterchainAccountAddressRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryInterchainAccountAddressRequest proto.InternalMessageInfo - -// Query response for an interchain account address -type QueryInterchainAccountAddressResponse struct { - // The corresponding interchain account address on the host chain - InterchainAccountAddress string `protobuf:"bytes,1,opt,name=interchain_account_address,json=interchainAccountAddress,proto3" json:"interchain_account_address,omitempty"` -} - -func (m *QueryInterchainAccountAddressResponse) Reset() { *m = QueryInterchainAccountAddressResponse{} } -func (m *QueryInterchainAccountAddressResponse) String() string { return proto.CompactTextString(m) } -func (*QueryInterchainAccountAddressResponse) ProtoMessage() {} -func (*QueryInterchainAccountAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_72a16b57c3343764, []int{1} -} -func (m *QueryInterchainAccountAddressResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryInterchainAccountAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryInterchainAccountAddressResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *QueryInterchainAccountAddressResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryInterchainAccountAddressResponse.Merge(m, src) -} -func (m *QueryInterchainAccountAddressResponse) XXX_Size() int { - return m.Size() -} -func (m *QueryInterchainAccountAddressResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryInterchainAccountAddressResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryInterchainAccountAddressResponse proto.InternalMessageInfo - -func (m *QueryInterchainAccountAddressResponse) GetInterchainAccountAddress() string { - if m != nil { - return m.InterchainAccountAddress - } - return "" -} - -func init() { - proto.RegisterType((*QueryInterchainAccountAddressRequest)(nil), "ibc.applications.interchain_accounts.v1.QueryInterchainAccountAddressRequest") - proto.RegisterType((*QueryInterchainAccountAddressResponse)(nil), "ibc.applications.interchain_accounts.v1.QueryInterchainAccountAddressResponse") -} - -func init() { - proto.RegisterFile("ibc/applications/interchain_accounts/v1/query.proto", fileDescriptor_72a16b57c3343764) -} - -var fileDescriptor_72a16b57c3343764 = []byte{ - // 349 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x52, 0xbf, 0x4a, 0x03, 0x31, - 0x18, 0xbf, 0x0c, 0x8a, 0x66, 0x3c, 0x3a, 0x94, 0x43, 0xae, 0x52, 0x14, 0x5d, 0x9a, 0xd8, 0x16, - 0x11, 0xc4, 0xa5, 0x82, 0x43, 0x07, 0x45, 0x3b, 0xba, 0x1c, 0xb9, 0x5c, 0xb8, 0x06, 0xda, 0x7c, - 0x69, 0x92, 0x2b, 0xf4, 0x0d, 0x1c, 0x7d, 0x84, 0xbe, 0x87, 0xb3, 0xe0, 0xd8, 0xd1, 0x51, 0xda, - 0xc5, 0xc7, 0x90, 0xde, 0x95, 0x5a, 0xd0, 0xe2, 0x0d, 0x6e, 0x1f, 0xf9, 0x7d, 0xbf, 0x3f, 0x7c, - 0xbf, 0xe0, 0xb6, 0x8c, 0x39, 0x65, 0x5a, 0x0f, 0x24, 0x67, 0x4e, 0x82, 0xb2, 0x54, 0x2a, 0x27, - 0x0c, 0xef, 0x33, 0xa9, 0x22, 0xc6, 0x39, 0x64, 0xca, 0x59, 0x3a, 0x6e, 0xd2, 0x51, 0x26, 0xcc, - 0x84, 0x68, 0x03, 0x0e, 0xfc, 0x13, 0x19, 0x73, 0xb2, 0x49, 0x22, 0xbf, 0x90, 0xc8, 0xb8, 0x19, - 0x54, 0x52, 0x48, 0x21, 0xe7, 0xd0, 0xe5, 0x54, 0xd0, 0x83, 0x83, 0x14, 0x20, 0x1d, 0x08, 0xca, - 0xb4, 0xa4, 0x4c, 0x29, 0x70, 0x2b, 0x91, 0x02, 0x3d, 0x2f, 0x9b, 0x68, 0x35, 0x17, 0xb4, 0x7a, - 0x8c, 0x8f, 0x1e, 0x96, 0x11, 0xbb, 0xeb, 0xe5, 0x4e, 0x81, 0x77, 0x92, 0xc4, 0x08, 0x6b, 0x7b, - 0x62, 0x94, 0x09, 0xeb, 0xfc, 0x33, 0x5c, 0xc9, 0x9f, 0x85, 0xd1, 0xcc, 0xb8, 0x49, 0xa4, 0xc1, - 0xb8, 0x48, 0x26, 0x55, 0x74, 0x88, 0x4e, 0xf7, 0x7b, 0xfe, 0x26, 0x76, 0x0f, 0xc6, 0x75, 0x93, - 0xcb, 0xbd, 0xa7, 0x69, 0xcd, 0xfb, 0x9c, 0xd6, 0xbc, 0xba, 0xc0, 0xc7, 0x7f, 0x78, 0x58, 0x0d, - 0xca, 0x0a, 0xff, 0x0a, 0x07, 0x3f, 0x43, 0x47, 0xac, 0xd8, 0x5a, 0x59, 0x55, 0xe5, 0x16, 0x95, - 0xd6, 0x2b, 0xc2, 0x3b, 0xb9, 0x8f, 0xff, 0x82, 0x70, 0x75, 0x9b, 0x99, 0x7f, 0x4b, 0x4a, 0xd6, - 0x40, 0xca, 0x1c, 0x26, 0xb8, 0xfb, 0x2f, 0xb9, 0xe2, 0x06, 0x75, 0xef, 0x3a, 0x7a, 0x9b, 0x87, - 0x68, 0x36, 0x0f, 0xd1, 0xc7, 0x3c, 0x44, 0xcf, 0x8b, 0xd0, 0x9b, 0x2d, 0x42, 0xef, 0x7d, 0x11, - 0x7a, 0x8f, 0x37, 0xa9, 0x74, 0xfd, 0x2c, 0x26, 0x1c, 0x86, 0x94, 0x83, 0x1d, 0x82, 0xa5, 0x32, - 0xe6, 0x8d, 0x14, 0xe8, 0xb8, 0x45, 0x87, 0x90, 0x64, 0x03, 0x61, 0x97, 0x7f, 0xc0, 0xd2, 0xd6, - 0x45, 0xe3, 0x3b, 0x45, 0x63, 0x5d, 0xbf, 0x9b, 0x68, 0x61, 0xe3, 0xdd, 0xbc, 0xfa, 0xf6, 0x57, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x38, 0xd5, 0xa3, 0x36, 0xc5, 0x02, 0x00, 0x00, -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// QueryClient is the client API for Query service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type QueryClient interface { - // Query to get the address of an interchain account - InterchainAccountAddress(ctx context.Context, in *QueryInterchainAccountAddressRequest, opts ...grpc.CallOption) (*QueryInterchainAccountAddressResponse, error) -} - -type queryClient struct { - cc grpc1.ClientConn -} - -func NewQueryClient(cc grpc1.ClientConn) QueryClient { - return &queryClient{cc} -} - -func (c *queryClient) InterchainAccountAddress(ctx context.Context, in *QueryInterchainAccountAddressRequest, opts ...grpc.CallOption) (*QueryInterchainAccountAddressResponse, error) { - out := new(QueryInterchainAccountAddressResponse) - err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.v1.Query/InterchainAccountAddress", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// QueryServer is the server API for Query service. -type QueryServer interface { - // Query to get the address of an interchain account - InterchainAccountAddress(context.Context, *QueryInterchainAccountAddressRequest) (*QueryInterchainAccountAddressResponse, error) -} - -// UnimplementedQueryServer can be embedded to have forward compatible implementations. -type UnimplementedQueryServer struct { -} - -func (*UnimplementedQueryServer) InterchainAccountAddress(ctx context.Context, req *QueryInterchainAccountAddressRequest) (*QueryInterchainAccountAddressResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method InterchainAccountAddress not implemented") -} - -func RegisterQueryServer(s grpc1.Server, srv QueryServer) { - s.RegisterService(&_Query_serviceDesc, srv) -} - -func _Query_InterchainAccountAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryInterchainAccountAddressRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).InterchainAccountAddress(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/ibc.applications.interchain_accounts.v1.Query/InterchainAccountAddress", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).InterchainAccountAddress(ctx, req.(*QueryInterchainAccountAddressRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Query_serviceDesc = grpc.ServiceDesc{ - ServiceName: "ibc.applications.interchain_accounts.v1.Query", - HandlerType: (*QueryServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "InterchainAccountAddress", - Handler: _Query_InterchainAccountAddress_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "ibc/applications/interchain_accounts/v1/query.proto", -} - -func (m *QueryInterchainAccountAddressRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryInterchainAccountAddressRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryInterchainAccountAddressRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.CounterpartyPortId) > 0 { - i -= len(m.CounterpartyPortId) - copy(dAtA[i:], m.CounterpartyPortId) - i = encodeVarintQuery(dAtA, i, uint64(len(m.CounterpartyPortId))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *QueryInterchainAccountAddressResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryInterchainAccountAddressResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryInterchainAccountAddressResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.InterchainAccountAddress) > 0 { - i -= len(m.InterchainAccountAddress) - copy(dAtA[i:], m.InterchainAccountAddress) - i = encodeVarintQuery(dAtA, i, uint64(len(m.InterchainAccountAddress))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { - offset -= sovQuery(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *QueryInterchainAccountAddressRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.CounterpartyPortId) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - return n -} - -func (m *QueryInterchainAccountAddressResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.InterchainAccountAddress) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - return n -} - -func sovQuery(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozQuery(x uint64) (n int) { - return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *QueryInterchainAccountAddressRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryInterchainAccountAddressRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryInterchainAccountAddressRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyPortId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CounterpartyPortId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *QueryInterchainAccountAddressResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryInterchainAccountAddressResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryInterchainAccountAddressResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field InterchainAccountAddress", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.InterchainAccountAddress = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipQuery(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowQuery - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowQuery - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowQuery - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthQuery - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupQuery - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthQuery - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") -) diff --git a/modules/apps/27-interchain-accounts/types/validate.go b/modules/apps/27-interchain-accounts/types/version.go similarity index 72% rename from modules/apps/27-interchain-accounts/types/validate.go rename to modules/apps/27-interchain-accounts/types/version.go index 0d36f2cd026..0cf8e2623cd 100644 --- a/modules/apps/27-interchain-accounts/types/validate.go +++ b/modules/apps/27-interchain-accounts/types/version.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "regexp" "strings" @@ -14,6 +15,21 @@ var DefaultMaxAddrLength = 128 // strictly alphanumeric characters var IsValidAddr = regexp.MustCompile("^[a-zA-Z0-9]*$").MatchString +// NewVersion returns a complete version string in the format: VersionPrefix + Delimter + AccAddress +func NewAppVersion(versionPrefix, accAddr string) string { + return fmt.Sprint(versionPrefix, Delimiter, accAddr) +} + +// ParseAddressFromVersion attempts to extract the associated account address from the provided version string +func ParseAddressFromVersion(version string) (string, error) { + s := strings.Split(version, Delimiter) + if len(s) != 2 { + return "", sdkerrors.Wrap(ErrInvalidVersion, "failed to parse version") + } + + return s[1], nil +} + // ValidateVersion performs basic validation of the provided ics27 version string. // An ics27 version string may include an optional account address as per [TODO: Add spec when available] // ValidateVersion first attempts to split the version string using the standard delimiter, then asserts a supported diff --git a/modules/apps/27-interchain-accounts/types/validate_test.go b/modules/apps/27-interchain-accounts/types/version_test.go similarity index 60% rename from modules/apps/27-interchain-accounts/types/validate_test.go rename to modules/apps/27-interchain-accounts/types/version_test.go index 64e2e4fcf1b..0ad4a235eb7 100644 --- a/modules/apps/27-interchain-accounts/types/validate_test.go +++ b/modules/apps/27-interchain-accounts/types/version_test.go @@ -1,9 +1,54 @@ package types_test import ( + "fmt" + "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" ) +func (suite *TypesTestSuite) TestParseAddressFromVersion() { + + testCases := []struct { + name string + version string + expValue string + expPass bool + }{ + { + "success", + types.NewAppVersion(types.VersionPrefix, TestOwnerAddress), + TestOwnerAddress, + true, + }, + { + "failed to parse address from version", + "invalid-version-string", + "", + false, + }, + { + "failure with multiple delimiters", + fmt.Sprint(types.NewAppVersion(types.VersionPrefix, TestOwnerAddress), types.Delimiter, types.NewAppVersion(types.VersionPrefix, TestOwnerAddress)), + "", + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + addr, err := types.ParseAddressFromVersion(tc.version) + + if tc.expPass { + suite.Require().Equal(tc.expValue, addr) + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Empty(addr) + suite.Require().Error(err, tc.name) + } + }) + } +} + func (suite *TypesTestSuite) TestValidateVersion() { testCases := []struct { name string diff --git a/proto/ibc/applications/interchain_accounts/v1/genesis.proto b/proto/ibc/applications/interchain_accounts/v1/genesis.proto index 5645e726a3d..bc3ee7d242f 100644 --- a/proto/ibc/applications/interchain_accounts/v1/genesis.proto +++ b/proto/ibc/applications/interchain_accounts/v1/genesis.proto @@ -8,11 +8,24 @@ import "gogoproto/gogo.proto"; // GenesisState defines the interchain accounts genesis state message GenesisState { + ControllerGenesisState controller_genesis_state = 1 [(gogoproto.moretags) = "yaml:\"controller_genesis_state\""]; + HostGenesisState host_genesis_state = 2 [(gogoproto.moretags) = "yaml:\"host_genesis_state\""]; +} + +// ControllerGenesisState defines the interchain accounts controller genesis state +message ControllerGenesisState { repeated ActiveChannel active_channels = 1 [(gogoproto.moretags) = "yaml:\"active_channels\""]; repeated RegisteredInterchainAccount interchain_accounts = 2 [(gogoproto.moretags) = "yaml:\"interchain_accounts\""]; repeated string ports = 3; } +// HostGenesisState defines the interchain accounts host genesis state +message HostGenesisState { + repeated ActiveChannel active_channels = 1 [(gogoproto.moretags) = "yaml:\"active_channels\""]; + repeated RegisteredInterchainAccount interchain_accounts = 2 [(gogoproto.moretags) = "yaml:\"interchain_accounts\""]; + string port = 3; +} + // ActiveChannel contains a pairing of port ID and channel ID for an active interchain accounts channel message ActiveChannel { string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; diff --git a/proto/ibc/applications/interchain_accounts/v1/query.proto b/proto/ibc/applications/interchain_accounts/v1/query.proto deleted file mode 100644 index a3b77506794..00000000000 --- a/proto/ibc/applications/interchain_accounts/v1/query.proto +++ /dev/null @@ -1,30 +0,0 @@ -syntax = "proto3"; - -package ibc.applications.interchain_accounts.v1; - -option go_package = "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types"; - -import "gogoproto/gogo.proto"; -import "google/api/annotations.proto"; -import "ibc/applications/interchain_accounts/v1/account.proto"; - -// Query defines the gRPC querier service. -service Query { - // Query to get the address of an interchain account - rpc InterchainAccountAddress(QueryInterchainAccountAddressRequest) returns (QueryInterchainAccountAddressResponse) {} -} - -// Query request for an interchain account address -message QueryInterchainAccountAddressRequest { - option (gogoproto.equal) = false; - option (gogoproto.goproto_getters) = false; - - // Counterparty PortID is the portID on the controller chain - string counterparty_port_id = 1; -} - -// Query response for an interchain account address -message QueryInterchainAccountAddressResponse { - // The corresponding interchain account address on the host chain - string interchain_account_address = 1; -} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 3050e6436d6..1955558be31 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -81,7 +81,12 @@ import ( upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" ica "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts" - icakeeper "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/keeper" + icacontroller "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/host/types" icatypes "github.com/cosmos/ibc-go/v2/modules/apps/27-interchain-accounts/types" transfer "github.com/cosmos/ibc-go/v2/modules/apps/transfer" ibctransferkeeper "github.com/cosmos/ibc-go/v2/modules/apps/transfer/keeper" @@ -173,30 +178,32 @@ type SimApp struct { memKeys map[string]*sdk.MemoryStoreKey // keepers - AccountKeeper authkeeper.AccountKeeper - BankKeeper bankkeeper.Keeper - CapabilityKeeper *capabilitykeeper.Keeper - StakingKeeper stakingkeeper.Keeper - SlashingKeeper slashingkeeper.Keeper - MintKeeper mintkeeper.Keeper - DistrKeeper distrkeeper.Keeper - GovKeeper govkeeper.Keeper - CrisisKeeper crisiskeeper.Keeper - UpgradeKeeper upgradekeeper.Keeper - ParamsKeeper paramskeeper.Keeper - AuthzKeeper authzkeeper.Keeper - IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly - ICAKeeper icakeeper.Keeper - EvidenceKeeper evidencekeeper.Keeper - TransferKeeper ibctransferkeeper.Keeper - FeeGrantKeeper feegrantkeeper.Keeper + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + CapabilityKeeper *capabilitykeeper.Keeper + StakingKeeper stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + CrisisKeeper crisiskeeper.Keeper + UpgradeKeeper upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + AuthzKeeper authzkeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + EvidenceKeeper evidencekeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper // make scoped keepers public for test purposes - ScopedIBCKeeper capabilitykeeper.ScopedKeeper - ScopedTransferKeeper capabilitykeeper.ScopedKeeper - ScopedICAKeeper capabilitykeeper.ScopedKeeper - ScopedIBCMockKeeper capabilitykeeper.ScopedKeeper - ScopedICAMockKeeper capabilitykeeper.ScopedKeeper + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper + ScopedICAHostKeeper capabilitykeeper.ScopedKeeper + ScopedIBCMockKeeper capabilitykeeper.ScopedKeeper + ScopedICAMockKeeper capabilitykeeper.ScopedKeeper // make IBC modules public for test purposes // these modules are never directly routed to by the IBC Router @@ -241,7 +248,7 @@ func NewSimApp( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, - evidencetypes.StoreKey, ibctransfertypes.StoreKey, icatypes.StoreKey, capabilitytypes.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, capabilitytypes.StoreKey, authzkeeper.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) @@ -267,12 +274,13 @@ func NewSimApp( app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) - scopedICAKeeper := app.CapabilityKeeper.ScopeToModule(icatypes.ModuleName) + scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.ModuleName) + scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.ModuleName) // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do // not replicate if you do not need to test core IBC or light clients. scopedIBCMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName) - scopedICAMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName + icatypes.ModuleName) + scopedICAMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName + icacontrollertypes.ModuleName) // seal capability keeper after scoping modules app.CapabilityKeeper.Seal() @@ -344,24 +352,33 @@ func NewSimApp( mockModule := ibcmock.NewAppModule(scopedIBCMockKeeper, &app.IBCKeeper.PortKeeper) mockIBCModule := ibcmock.NewIBCModule(&ibcmock.MockIBCApp{}, scopedIBCMockKeeper) - app.ICAKeeper = icakeeper.NewKeeper( - appCodec, keys[icatypes.StoreKey], + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, - app.AccountKeeper, scopedICAKeeper, app.MsgServiceRouter(), + app.AccountKeeper, scopedICAControllerKeeper, app.MsgServiceRouter(), ) - icaModule := ica.NewAppModule(app.ICAKeeper) + + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), + ) + + icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) // initialize ICA module with mock module as the authentication module on the controller side icaAuthModule := ibcmock.NewIBCModule(&ibcmock.MockIBCApp{}, scopedICAMockKeeper) app.ICAAuthModule = icaAuthModule - icaIBCModule := ica.NewIBCModule(app.ICAKeeper, icaAuthModule) + icaControllerIBCModule := icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthModule) + icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) - // Create static IBC router, add transfer route, then set and seal it + // Create static IBC router, add app routes, then set and seal it ibcRouter := porttypes.NewRouter() - ibcRouter.AddRoute(icatypes.ModuleName, icaIBCModule). - AddRoute(ibcmock.ModuleName+icatypes.ModuleName, icaIBCModule). // ica with mock auth module stack route to ica (top level of middleware stack) + ibcRouter.AddRoute(icacontrollertypes.ModuleName, icaControllerIBCModule). + AddRoute(icahosttypes.ModuleName, icaHostIBCModule). + AddRoute(ibcmock.ModuleName+icacontrollertypes.ModuleName, icaControllerIBCModule). // ica with mock auth module stack route to ica (top level of middleware stack) AddRoute(ibctransfertypes.ModuleName, transferIBCModule). AddRoute(ibcmock.ModuleName, mockIBCModule) app.IBCKeeper.SetRouter(ibcRouter) @@ -494,7 +511,8 @@ func NewSimApp( app.ScopedIBCKeeper = scopedIBCKeeper app.ScopedTransferKeeper = scopedTransferKeeper - app.ScopedICAKeeper = scopedICAKeeper + app.ScopedICAControllerKeeper = scopedICAControllerKeeper + app.ScopedICAHostKeeper = scopedICAHostKeeper // 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.