mukan-ibc/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

936 lines
32 KiB
Go

package controller_test
import (
"errors"
"strconv"
"testing"
testifysuite "github.com/stretchr/testify/suite"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/controller"
controllerkeeper "git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/controller/keeper"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/controller/types"
icatypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/types"
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
porttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/05-port/types"
host "git.cw.tr/mukan-network/mukan-ibc/modules/core/24-host"
ibcerrors "git.cw.tr/mukan-network/mukan-ibc/modules/core/errors"
ibctesting "git.cw.tr/mukan-network/mukan-ibc/testing"
)
const invalidVersion = "invalid|version"
var (
// TestOwnerAddress defines a reusable bech32 address for testing purposes
TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs"
// TestPortID defines a reusable port identifier for testing purposes
TestPortID, _ = icatypes.NewControllerPortID(TestOwnerAddress)
// TestVersion defines a reusable interchainaccounts version string for testing purposes
TestVersion = icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID)
)
type InterchainAccountsTestSuite struct {
testifysuite.Suite
coordinator *ibctesting.Coordinator
// testing chains used for convenience and readability
chainA *ibctesting.TestChain
chainB *ibctesting.TestChain
chainC *ibctesting.TestChain
}
func TestICATestSuite(t *testing.T) {
testifysuite.Run(t, new(InterchainAccountsTestSuite))
}
func (suite *InterchainAccountsTestSuite) SetupTest() {
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3)
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2))
suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3))
}
func NewICAPath(chainA, chainB *ibctesting.TestChain, ordering channeltypes.Order) *ibctesting.Path {
path := ibctesting.NewPath(chainA, chainB)
path.EndpointA.ChannelConfig.PortID = icatypes.HostPortID
path.EndpointB.ChannelConfig.PortID = icatypes.HostPortID
path.EndpointA.ChannelConfig.Order = ordering
path.EndpointB.ChannelConfig.Order = ordering
path.EndpointA.ChannelConfig.Version = TestVersion
path.EndpointB.ChannelConfig.Version = TestVersion
return path
}
func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error {
portID, err := icatypes.NewControllerPortID(owner)
if err != nil {
return err
}
channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext())
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, TestVersion, endpoint.ChannelConfig.Order); err != nil {
return err
}
// commit state changes for proof verification
endpoint.Chain.NextBlock()
// update port/channel ids
endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence)
endpoint.ChannelConfig.PortID = portID
endpoint.ChannelConfig.Version = TestVersion
return nil
}
// SetupICAPath invokes the InterchainAccounts entrypoint and subsequent channel handshake handlers
func SetupICAPath(path *ibctesting.Path, owner string) error {
if err := RegisterInterchainAccount(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
}
return path.EndpointB.ChanOpenConfirm()
}
func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() {
var (
channel *channeltypes.Channel
isNilApp bool
path *ibctesting.Path
)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success", func() {}, nil,
},
{
"ICA auth module modification of channel version is ignored", func() {
// NOTE: explicitly modify the channel version via the auth module callback,
// ensuring the expected JSON encoded metadata is not modified upon return
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
portID, channelID string,
counterparty channeltypes.Counterparty, version string,
) (string, error) {
return "invalid-version", nil
}
}, nil,
},
{
"controller submodule disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false))
}, types.ErrControllerSubModuleDisabled,
},
{
"ICA auth module callback fails", func() {
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
portID, channelID string,
counterparty channeltypes.Counterparty, version string,
) (string, error) {
return "", errors.New("mock ica auth fails")
}
}, errors.New("mock ica auth fails"),
},
{
"nil underlying app", func() {
isNilApp = true
}, nil,
},
{
"middleware disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID)
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
portID, channelID string,
counterparty channeltypes.Counterparty, version string,
) (string, error) {
return "", errors.New("error should be unreachable")
}
}, nil,
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
isNilApp = false
path = NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
// mock init interchain account
portID, err := icatypes.NewControllerPortID(TestOwnerAddress)
suite.Require().NoError(err)
path.EndpointA.ChannelConfig.PortID = portID
path.EndpointA.ChannelID = ibctesting.FirstChannelID
suite.chainA.GetSimApp().ICAControllerKeeper.SetMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID)
// default values
counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
channel = &channeltypes.Channel{
State: channeltypes.INIT,
Ordering: ordering,
Counterparty: counterparty,
ConnectionHops: []string{path.EndpointA.ConnectionID},
Version: path.EndpointA.ChannelConfig.Version,
}
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)
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
if isNilApp {
cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper)
}
version, err := cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.ConnectionHops,
path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel.Counterparty, channel.Version,
)
if tc.expErr == nil {
suite.Require().Equal(TestVersion, version)
suite.Require().NoError(err)
} else {
suite.Require().ErrorContains(err, tc.expErr.Error())
}
})
}
}
}
// Test initiating a ChanOpenTry using the controller chain instead of the host chain
// ChainA is the controller chain. ChainB creates a controller port as well,
// attempting to trick chainA.
// Sending a MsgChanOpenTry will never reach the application callback due to
// core IBC checks not passing, so a call to the application callback is also
// done directly.
func (suite *InterchainAccountsTestSuite) TestChanOpenTry() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress)
suite.Require().NoError(err)
// chainB also creates a controller port
err = RegisterInterchainAccount(path.EndpointB, TestOwnerAddress)
suite.Require().NoError(err)
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
initProof, proofHeight := path.EndpointB.Chain.QueryProof(channelKey)
// use chainA (controller) for ChanOpenTry
msg := channeltypes.NewMsgChannelOpenTry(path.EndpointA.ChannelConfig.PortID, TestVersion, ordering, []string{path.EndpointA.ConnectionID}, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, TestVersion, initProof, proofHeight, icatypes.ModuleName)
handler := suite.chainA.GetSimApp().MsgServiceRouter().Handler(msg)
_, err = handler(suite.chainA.GetContext(), msg)
suite.Require().Error(err)
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
version, err := cbs.OnChanOpenTry(
suite.chainA.GetContext(), path.EndpointA.ChannelConfig.Order, []string{path.EndpointA.ConnectionID},
path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID,
counterparty, path.EndpointB.ChannelConfig.Version,
)
suite.Require().Error(err)
suite.Require().Equal("", version)
}
}
func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() {
var (
path *ibctesting.Path
isNilApp bool
)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success", func() {}, nil,
},
{
"controller submodule disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false))
}, types.ErrControllerSubModuleDisabled,
},
{
"ICA OnChanOpenACK fails - invalid version", func() {
path.EndpointB.ChannelConfig.Version = invalidVersion
}, ibcerrors.ErrInvalidType,
},
{
"ICA auth module callback fails", func() {
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenAck = func(
ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string,
) error {
return errors.New("mock ica auth fails")
}
}, errors.New("mock ica auth fails"),
},
{
"nil underlying app", func() {
isNilApp = true
}, nil,
},
{
"middleware disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID)
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenAck = func(
ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string,
) error {
return errors.New("error should be unreachable")
}
}, nil,
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
isNilApp = false
path = NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress)
suite.Require().NoError(err)
err = path.EndpointB.ChanOpenTry()
suite.Require().NoError(err)
tc.malleate() // malleate mutates test data
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelID, path.EndpointB.ChannelConfig.Version)
if isNilApp {
cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper)
}
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorContains(err, tc.expErr.Error())
}
})
}
}
}
// Test initiating a ChanOpenConfirm using the controller chain instead of the host chain
// ChainA is the controller chain. ChainB is the host chain
// Sending a MsgChanOpenConfirm will never reach the application callback due to
// core IBC checks not passing, so a call to the application callback is also
// done directly.
func (suite *InterchainAccountsTestSuite) TestChanOpenConfirm() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress)
suite.Require().NoError(err)
err = path.EndpointB.ChanOpenTry()
suite.Require().NoError(err)
// chainB maliciously sets channel to OPEN
channel := channeltypes.NewChannel(channeltypes.OPEN, ordering, channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointB.ConnectionID}, TestVersion)
suite.chainB.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel)
// commit state changes so proof can be created
suite.chainB.NextBlock()
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// query proof from ChainB
channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
ackProof, proofHeight := path.EndpointB.Chain.QueryProof(channelKey)
// use chainA (controller) for ChanOpenConfirm
msg := channeltypes.NewMsgChannelOpenConfirm(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ackProof, proofHeight, icatypes.ModuleName)
handler := suite.chainA.GetSimApp().MsgServiceRouter().Handler(msg)
_, err = handler(suite.chainA.GetContext(), msg)
suite.Require().Error(err)
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
err = cbs.OnChanOpenConfirm(
suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID,
)
suite.Require().Error(err)
}
}
// OnChanCloseInit on controller (chainA)
func (suite *InterchainAccountsTestSuite) TestOnChanCloseInit() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
err = cbs.OnChanCloseInit(
suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID,
)
suite.Require().Error(err)
}
}
func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() {
var (
path *ibctesting.Path
isNilApp bool
)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success", func() {}, nil,
},
{
"nil underlying app", func() {
isNilApp = true
}, nil,
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
isNilApp = false
path = NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)
tc.malleate() // malleate mutates test data
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
if isNilApp {
cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper)
}
err = cbs.OnChanCloseConfirm(
suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() {
testCases := []struct {
name string
malleate func()
expSuccess bool
}{
{
"ICA OnRecvPacket fails with ErrInvalidChannelFlow", func() {}, false,
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)
tc.malleate() // malleate mutates test data
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
packet := channeltypes.NewPacket(
[]byte("empty packet data"),
suite.chainB.SenderAccount.GetSequence(),
path.EndpointB.ChannelConfig.PortID,
path.EndpointB.ChannelID,
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
clienttypes.NewHeight(0, 100),
0,
)
ctx := suite.chainA.GetContext()
ack := cbs.OnRecvPacket(ctx, path.EndpointA.GetChannel().Version, packet, nil)
suite.Require().Equal(tc.expSuccess, ack.Success())
expectedEvents := sdk.Events{
sdk.NewEvent(
icatypes.EventTypePacket,
sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName),
sdk.NewAttribute(icatypes.AttributeKeyControllerChannelID, packet.GetDestChannel()),
sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, strconv.FormatBool(false)),
sdk.NewAttribute(icatypes.AttributeKeyAckError, "cannot receive packet on controller chain: invalid message sent to channel end"),
),
}.ToABCIEvents()
expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{})
ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents())
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() {
var (
path *ibctesting.Path
isNilApp bool
)
testCases := []struct {
msg string
malleate func()
expErr error
}{
{
"success",
func() {},
nil,
},
{
"controller submodule disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false))
}, types.ErrControllerSubModuleDisabled,
},
{
"ICA auth module callback fails", func() {
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnAcknowledgementPacket = func(
ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress,
) error {
return errors.New("mock ica auth fails")
}
}, errors.New("mock ica auth fails"),
},
{
"nil underlying app", func() {
isNilApp = true
}, nil,
},
{
"middleware disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID)
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnAcknowledgementPacket = func(
ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress,
) error {
return errors.New("error should be unreachable")
}
}, nil,
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.msg, func() {
suite.SetupTest() // reset
isNilApp = false
path = NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)
packet := channeltypes.NewPacket(
[]byte("empty packet data"),
suite.chainA.SenderAccount.GetSequence(),
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
path.EndpointB.ChannelConfig.PortID,
path.EndpointB.ChannelID,
clienttypes.NewHeight(0, 100),
0,
)
tc.malleate() // malleate mutates test data
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
if isNilApp {
cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper)
}
err = cbs.OnAcknowledgementPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, []byte("ack"), nil)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorContains(err, tc.expErr.Error())
}
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() {
var (
path *ibctesting.Path
isNilApp bool
)
testCases := []struct {
msg string
malleate func()
expErr error
}{
{
"success",
func() {},
nil,
},
{
"controller submodule disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false))
}, types.ErrControllerSubModuleDisabled,
},
{
"ICA auth module callback fails", func() {
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnTimeoutPacket = func(
ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress,
) error {
return errors.New("mock ica auth fails")
}
}, errors.New("mock ica auth fails"),
},
{
"nil underlying app", func() {
isNilApp = true
}, nil,
},
{
"middleware disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID)
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnTimeoutPacket = func(
ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress,
) error {
return errors.New("error should be unreachable")
}
}, nil,
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.msg, func() {
suite.SetupTest() // reset
isNilApp = false
path = NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)
packet := channeltypes.NewPacket(
[]byte("empty packet data"),
suite.chainA.SenderAccount.GetSequence(),
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
path.EndpointB.ChannelConfig.PortID,
path.EndpointB.ChannelID,
clienttypes.NewHeight(0, 100),
0,
)
tc.malleate() // malleate mutates test data
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
if isNilApp {
cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper)
}
err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, nil)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorContains(err, tc.expErr.Error())
}
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestSingleHostMultipleControllers() {
var (
pathAToB *ibctesting.Path
pathCToB *ibctesting.Path
)
testCases := []struct {
msg string
malleate func()
}{
{
"success",
func() {},
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.msg, func() {
// reset
suite.SetupTest()
TestVersion = icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID)
// Setup a new path from A(controller) -> B(host)
pathAToB = NewICAPath(suite.chainA, suite.chainB, ordering)
pathAToB.SetupConnections()
err := SetupICAPath(pathAToB, TestOwnerAddress)
suite.Require().NoError(err)
// Setup a new path from C(controller) -> B(host)
pathCToB = NewICAPath(suite.chainC, suite.chainB, ordering)
pathCToB.SetupConnections()
// NOTE: Here the version metadata is overridden to include to the next host connection sequence (i.e. chainB's connection to chainC)
// SetupICAPath() will set endpoint.ChannelConfig.Version to TestVersion
TestVersion = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{
Version: icatypes.Version,
ControllerConnectionId: pathCToB.EndpointA.ConnectionID,
HostConnectionId: pathCToB.EndpointB.ConnectionID,
Encoding: icatypes.EncodingProtobuf,
TxType: icatypes.TxTypeSDKMultiMsg,
}))
err = SetupICAPath(pathCToB, TestOwnerAddress)
suite.Require().NoError(err)
tc.malleate() // malleate mutates test data
accAddressChainA, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), pathAToB.EndpointB.ConnectionID, pathAToB.EndpointA.ChannelConfig.PortID)
suite.Require().True(found)
accAddressChainC, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), pathCToB.EndpointB.ConnectionID, pathCToB.EndpointA.ChannelConfig.PortID)
suite.Require().True(found)
suite.Require().NotEqual(accAddressChainA, accAddressChainC)
chainAChannelID, found := suite.chainB.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainB.GetContext(), pathAToB.EndpointB.ConnectionID, pathAToB.EndpointA.ChannelConfig.PortID)
suite.Require().True(found)
chainCChannelID, found := suite.chainB.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainB.GetContext(), pathCToB.EndpointB.ConnectionID, pathCToB.EndpointA.ChannelConfig.PortID)
suite.Require().True(found)
suite.Require().NotEqual(chainAChannelID, chainCChannelID)
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestGetAppVersion() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)
cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID)
suite.Require().True(ok)
controllerStack, ok := cbs.(porttypes.ICS4Wrapper)
suite.Require().True(ok)
appVersion, found := controllerStack.GetAppVersion(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
suite.Require().True(found)
suite.Require().Equal(path.EndpointA.ChannelConfig.Version, appVersion)
}
}
func (suite *InterchainAccountsTestSuite) TestInFlightHandshakeRespectsGoAPICaller() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
// initiate a channel handshake such that channel.State == INIT
err := RegisterInterchainAccount(path.EndpointA, suite.chainA.SenderAccount.GetAddress().String())
suite.Require().NoError(err)
// attempt to start a second handshake via the controller msg server
msgServer := controllerkeeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper)
msgRegisterInterchainAccount := types.NewMsgRegisterInterchainAccount(path.EndpointA.ConnectionID, suite.chainA.SenderAccount.GetAddress().String(), TestVersion, ordering)
res, err := msgServer.RegisterInterchainAccount(suite.chainA.GetContext(), msgRegisterInterchainAccount)
suite.Require().Error(err)
suite.Require().Nil(res)
}
}
func (suite *InterchainAccountsTestSuite) TestInFlightHandshakeRespectsMsgServerCaller() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
// initiate a channel handshake such that channel.State == INIT
msgServer := controllerkeeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper)
msgRegisterInterchainAccount := types.NewMsgRegisterInterchainAccount(path.EndpointA.ConnectionID, suite.chainA.SenderAccount.GetAddress().String(), TestVersion, ordering)
res, err := msgServer.RegisterInterchainAccount(suite.chainA.GetContext(), msgRegisterInterchainAccount)
suite.Require().NotNil(res)
suite.Require().NoError(err)
// attempt to start a second handshake via the legacy Go API
err = RegisterInterchainAccount(path.EndpointA, suite.chainA.SenderAccount.GetAddress().String())
suite.Require().Error(err)
}
}
func (suite *InterchainAccountsTestSuite) TestClosedChannelReopensWithMsgServer() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, suite.chainA.SenderAccount.GetAddress().String())
suite.Require().NoError(err)
// set the channel state to closed
path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED })
path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED })
// reset endpoint channel ids
path.EndpointA.ChannelID = ""
path.EndpointB.ChannelID = ""
// fetch the next channel sequence before reinitiating the channel handshake
channelSeq := suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(suite.chainA.GetContext())
// route a new MsgRegisterInterchainAccount in order to reopen the
msgServer := controllerkeeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper)
msgRegisterInterchainAccount := types.NewMsgRegisterInterchainAccount(path.EndpointA.ConnectionID, suite.chainA.SenderAccount.GetAddress().String(), path.EndpointA.ChannelConfig.Version, ordering)
res, err := msgServer.RegisterInterchainAccount(suite.chainA.GetContext(), msgRegisterInterchainAccount)
suite.Require().NoError(err)
suite.Require().Equal(channeltypes.FormatChannelIdentifier(channelSeq), res.ChannelId)
// assign the channel sequence to endpointA before generating proofs and initiating the TRY step
path.EndpointA.ChannelID = channeltypes.FormatChannelIdentifier(channelSeq)
path.EndpointA.Chain.NextBlock()
err = path.EndpointB.ChanOpenTry()
suite.Require().NoError(err)
err = path.EndpointA.ChanOpenAck()
suite.Require().NoError(err)
err = path.EndpointB.ChanOpenConfirm()
suite.Require().NoError(err)
}
}
func (suite *InterchainAccountsTestSuite) TestPacketDataUnmarshalerInterface() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)
expPacketData := icatypes.InterchainAccountPacketData{
Type: icatypes.EXECUTE_TX,
Data: []byte("data"),
Memo: "",
}
controllerMiddleware := controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper)
packetData, version, err := controllerMiddleware.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, expPacketData.GetBytes())
suite.Require().NoError(err)
suite.Require().Equal(version, path.EndpointA.ChannelConfig.Version)
suite.Require().Equal(expPacketData, packetData)
// test invalid packet data
invalidPacketData := []byte("invalid packet data")
// Context, port identifier and channel identifier are not used for controller.
packetData, version, err = controllerMiddleware.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, invalidPacketData)
suite.Require().Error(err)
suite.Require().Empty(version)
suite.Require().Nil(packetData)
}
}