mukan-ibc/modules/apps/27-interchain-accounts/host/ibc_module_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

755 lines
27 KiB
Go

package host_test
import (
"errors"
"strconv"
"testing"
"github.com/cosmos/gogoproto/proto"
testifysuite "github.com/stretchr/testify/suite"
sdkmath "cosmossdk.io/math"
codectypes "git.cw.tr/mukan-network/mukan-sdk/codec/types"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
banktypes "git.cw.tr/mukan-network/mukan-sdk/x/bank/types"
icahost "git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/host"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/host/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"
host "git.cw.tr/mukan-network/mukan-ibc/modules/core/24-host"
"git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
ibctesting "git.cw.tr/mukan-network/mukan-ibc/testing"
)
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 = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{
Version: icatypes.Version,
ControllerConnectionId: ibctesting.FirstConnectionID,
HostConnectionId: ibctesting.FirstConnectionID,
Encoding: icatypes.EncodingProtobuf,
TxType: icatypes.TxTypeSDKMultiMsg,
}))
)
type InterchainAccountsTestSuite struct {
testifysuite.Suite
coordinator *ibctesting.Coordinator
// testing chains used for convenience and readability
chainA *ibctesting.TestChain
chainB *ibctesting.TestChain
}
func TestICATestSuite(t *testing.T) {
testifysuite.Run(t, new(InterchainAccountsTestSuite))
}
func (suite *InterchainAccountsTestSuite) SetupTest() {
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2)
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2))
}
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, endpoint.ChannelConfig.Version, 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
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()
}
// Test initiating a ChanOpenInit using the host chain instead of the controller chain
// ChainA is the controller chain. ChainB is the host chain
func (suite *InterchainAccountsTestSuite) TestChanOpenInit() {
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
suite.SetupTest() // reset
path := NewICAPath(suite.chainA, suite.chainB, ordering)
path.SetupConnections()
// use chainB (host) for ChanOpenInit
msg := channeltypes.NewMsgChannelOpenInit(path.EndpointB.ChannelConfig.PortID, icatypes.Version, ordering, []string{path.EndpointB.ConnectionID}, path.EndpointA.ChannelConfig.PortID, icatypes.ModuleName)
handler := suite.chainB.GetSimApp().MsgServiceRouter().Handler(msg)
_, err := handler(suite.chainB.GetContext(), msg)
suite.Require().Error(err)
}
}
func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() {
var (
path *ibctesting.Path
channel *channeltypes.Channel
)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success", func() {}, nil,
},
{
"account address generation is block dependent", func() {
icaHostAccount := icatypes.GenerateAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID)
err := suite.chainB.GetSimApp().BankKeeper.SendCoins(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), icaHostAccount, sdk.Coins{sdk.NewCoin("stake", sdkmath.NewInt(1))})
suite.Require().NoError(err)
suite.Require().True(suite.chainB.GetSimApp().AccountKeeper.HasAccount(suite.chainB.GetContext(), icaHostAccount))
// ensure account registration is simulated in a separate block
suite.chainB.NextBlock()
}, nil,
},
{
"success: ICA auth module callback returns error", func() {
// mock module callback should not be called on host side
suite.chainB.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenTry = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
portID, channelID string,
counterparty channeltypes.Counterparty, counterpartyVersion string,
) (string, error) {
return "", errors.New("mock ica auth fails")
}
}, nil,
},
{
"host submodule disabled", func() {
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{}))
}, types.ErrHostSubModuleDisabled,
},
}
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 := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress)
suite.Require().NoError(err)
path.EndpointB.ChannelID = ibctesting.FirstChannelID
// default values
counterparty := channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
channel = &channeltypes.Channel{
State: channeltypes.TRYOPEN,
Ordering: ordering,
Counterparty: counterparty,
ConnectionHops: []string{path.EndpointB.ConnectionID},
Version: path.EndpointB.ChannelConfig.Version,
}
tc.malleate()
// ensure channel on chainB is set in state
suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, *channel)
cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
version, err := cbs.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.ConnectionHops,
path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel.Counterparty, path.EndpointA.ChannelConfig.Version,
)
if tc.expErr == nil {
suite.Require().NoError(err)
addr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, counterparty.PortId)
suite.Require().True(exists)
suite.Require().NotNil(addr)
} else {
suite.Require().ErrorIs(err, tc.expErr)
suite.Require().Equal("", version)
}
})
}
}
}
// Test initiating a ChanOpenAck using the host chain instead of the controller chain
// ChainA is the controller chain. ChainB is the host chain
func (suite *InterchainAccountsTestSuite) TestChanOpenAck() {
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)
// chainA maliciously sets channel to TRYOPEN
channel := channeltypes.NewChannel(channeltypes.TRYOPEN, channeltypes.ORDERED, channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{path.EndpointA.ConnectionID}, TestVersion)
suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel)
// commit state changes so proof can be created
suite.chainA.NextBlock()
err = path.EndpointB.UpdateClient()
suite.Require().NoError(err)
// query proof from ChainA
channelKey := host.ChannelKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
tryProof, proofHeight := path.EndpointA.Chain.QueryProof(channelKey)
// use chainB (host) for ChanOpenAck
msg := channeltypes.NewMsgChannelOpenAck(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelID, TestVersion, tryProof, proofHeight, icatypes.ModuleName)
handler := suite.chainB.GetSimApp().MsgServiceRouter().Handler(msg)
_, err = handler(suite.chainB.GetContext(), msg)
suite.Require().Error(err)
}
}
func (suite *InterchainAccountsTestSuite) TestOnChanOpenConfirm() {
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success", func() {}, nil,
},
{
"success: ICA auth module callback returns error", func() {
// mock module callback should not be called on host side
suite.chainB.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenConfirm = func(
ctx sdk.Context, portID, channelID string,
) error {
return errors.New("mock ica auth fails")
}
}, nil,
},
{
"host submodule disabled", func() {
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{}))
}, types.ErrHostSubModuleDisabled,
},
}
for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} {
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
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)
err = path.EndpointA.ChanOpenAck()
suite.Require().NoError(err)
tc.malleate()
cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
err = cbs.OnChanOpenConfirm(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
}
// OnChanCloseInit on host (chainB)
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.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
err = cbs.OnChanCloseInit(
suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID,
)
suite.Require().Error(err)
}
}
func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() {
var path *ibctesting.Path
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success", func() {}, nil,
},
}
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.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
err = cbs.OnChanCloseConfirm(
suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() {
var packetData []byte
testCases := []struct {
name string
malleate func()
expAckSuccess bool
eventErrorMsg string
}{
{
"success", func() {}, true, "",
},
{
"host submodule disabled", func() {
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{}))
}, false,
types.ErrHostSubModuleDisabled.Error(),
},
{
"success with ICA auth module callback failure", func() {
suite.chainB.GetSimApp().ICAAuthModule.IBCApp.OnRecvPacket = func(
ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress,
) exported.Acknowledgement {
return channeltypes.NewErrorAcknowledgement(errors.New("failed OnRecvPacket mock callback"))
}
}, true,
"failed OnRecvPacket mock callback",
},
{
"ICA OnRecvPacket fails - cannot unmarshal packet data", func() {
packetData = []byte("invalid data")
}, false,
"cannot unmarshal ICS-27 interchain account packet data: invalid type",
},
}
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)
// send 100stake to interchain account wallet
amount, _ := sdk.ParseCoinsNormalized("100stake")
interchainAccountAddr, _ := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID)
bankMsg := &banktypes.MsgSend{FromAddress: suite.chainB.SenderAccount.GetAddress().String(), ToAddress: interchainAccountAddr, Amount: amount}
_, err = suite.chainB.SendMsgs(bankMsg)
suite.Require().NoError(err)
// build packet data
msg := &banktypes.MsgSend{
FromAddress: interchainAccountAddr,
ToAddress: suite.chainB.SenderAccount.GetAddress().String(),
Amount: amount,
}
data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, icatypes.EncodingProtobuf)
suite.Require().NoError(err)
icaPacketData := icatypes.InterchainAccountPacketData{
Type: icatypes.EXECUTE_TX,
Data: data,
}
packetData = icaPacketData.GetBytes()
// build expected ack
protoAny, err := codectypes.NewAnyWithValue(&banktypes.MsgSendResponse{})
suite.Require().NoError(err)
expectedTxResponse, err := proto.Marshal(&sdk.TxMsgData{
MsgResponses: []*codectypes.Any{protoAny},
})
suite.Require().NoError(err)
expectedAck := channeltypes.NewResultAcknowledgement(expectedTxResponse)
params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)})
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params)
// malleate packetData for test cases
tc.malleate()
seq := uint64(1)
packet := channeltypes.NewPacket(packetData, seq, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0)
tc.malleate()
cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
ctx := suite.chainB.GetContext()
ack := cbs.OnRecvPacket(ctx, path.EndpointB.GetChannel().Version, packet, nil)
expectedAttributes := []sdk.Attribute{
sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName),
sdk.NewAttribute(icatypes.AttributeKeyHostChannelID, packet.GetDestChannel()),
sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, strconv.FormatBool(ack.Success())),
}
if tc.expAckSuccess {
suite.Require().True(ack.Success())
suite.Require().Equal(expectedAck, ack)
expectedEvents := sdk.Events{
sdk.NewEvent(
icatypes.EventTypePacket,
expectedAttributes...,
),
}.ToABCIEvents()
expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{})
ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents())
} else {
suite.Require().False(ack.Success())
expectedAttributes = append(expectedAttributes, sdk.NewAttribute(icatypes.AttributeKeyAckError, tc.eventErrorMsg))
expectedEvents := sdk.Events{
sdk.NewEvent(
icatypes.EventTypePacket,
expectedAttributes...,
),
}.ToABCIEvents()
expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{})
ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents())
}
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() {
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"ICA OnAcknowledgementPacket fails with ErrInvalidChannelFlow", func() {}, icatypes.ErrInvalidChannelFlow,
},
}
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.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
packet := channeltypes.NewPacket(
[]byte("empty packet data"),
suite.chainA.SenderAccount.GetSequence(),
path.EndpointB.ChannelConfig.PortID,
path.EndpointB.ChannelID,
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
clienttypes.NewHeight(0, 100),
0,
)
err = cbs.OnAcknowledgementPacket(suite.chainB.GetContext(), path.EndpointB.GetChannel().Version, packet, []byte("ackBytes"), nil)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
}
func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() {
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"ICA OnTimeoutPacket fails with ErrInvalidChannelFlow", func() {}, icatypes.ErrInvalidChannelFlow,
},
}
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.EndpointB.ChannelConfig.PortID)
suite.Require().True(ok)
packet := channeltypes.NewPacket(
[]byte("empty packet data"),
suite.chainA.SenderAccount.GetSequence(),
path.EndpointB.ChannelConfig.PortID,
path.EndpointB.ChannelID,
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
clienttypes.NewHeight(0, 100),
0,
)
err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, nil)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
}
func (suite *InterchainAccountsTestSuite) fundICAWallet(ctx sdk.Context, portID string, amount sdk.Coins) {
interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(ctx, ibctesting.FirstConnectionID, portID)
suite.Require().True(found)
msgBankSend := &banktypes.MsgSend{
FromAddress: suite.chainB.SenderAccount.GetAddress().String(),
ToAddress: interchainAccountAddr,
Amount: amount,
}
res, err := suite.chainB.SendMsgs(msgBankSend)
suite.Require().NotEmpty(res)
suite.Require().NoError(err)
}
// TestControlAccountAfterChannelClose tests that a controller chain can control a registered interchain account after the currently active channel for that interchain account has been closed.
// A new channel will be opened for the controller portID. The interchain account address should remain unchanged.
func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose() {
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)
// two sends will be performed, one after initial creation of the account and one after channel closure and reopening
var (
startingBal = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000)))
tokenAmt = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)))
expBalAfterFirstSend = startingBal.Sub(tokenAmt...)
expBalAfterSecondSend = expBalAfterFirstSend.Sub(tokenAmt...)
)
// check that the account is working as expected
suite.fundICAWallet(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID, startingBal)
interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID)
suite.Require().True(found)
msg := &banktypes.MsgSend{
FromAddress: interchainAccountAddr,
ToAddress: suite.chainB.SenderAccount.GetAddress().String(),
Amount: tokenAmt,
}
data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, icatypes.EncodingProtobuf)
suite.Require().NoError(err)
icaPacketData := icatypes.InterchainAccountPacketData{
Type: icatypes.EXECUTE_TX,
Data: data,
}
params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)})
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params)
// nolint: staticcheck // SA1019: ibctesting.FirstConnectionID is deprecated: use path.EndpointA.ConnectionID instead. (staticcheck)
_, err = suite.chainA.GetSimApp().ICAControllerKeeper.SendTx(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, icaPacketData, ^uint64(0))
suite.Require().NoError(err)
err = path.EndpointB.UpdateClient()
suite.Require().NoError(err)
// relay the packet
packetRelay := channeltypes.NewPacket(icaPacketData.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.ZeroHeight(), ^uint64(0))
err = path.RelayPacket(packetRelay)
suite.Require().NoError(err) // relay committed
// check that the ica balance is updated
icaAddr, err := sdk.AccAddressFromBech32(interchainAccountAddr)
suite.Require().NoError(err)
suite.assertBalance(icaAddr, expBalAfterFirstSend)
// close the channel
path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED })
path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED })
// open a new channel on the same port
path.EndpointA.ChannelID = ""
path.EndpointB.ChannelID = ""
path.CreateChannels()
// nolint: staticcheck // SA1019: ibctesting.FirstConnectionID is deprecated: use path.EndpointA.ConnectionID instead. (staticcheck)
_, err = suite.chainA.GetSimApp().ICAControllerKeeper.SendTx(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, icaPacketData, ^uint64(0))
suite.Require().NoError(err)
err = path.EndpointB.UpdateClient()
suite.Require().NoError(err)
// relay the packet
packetRelay = channeltypes.NewPacket(icaPacketData.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.ZeroHeight(), ^uint64(0))
err = path.RelayPacket(packetRelay)
suite.Require().NoError(err) // relay committed
suite.assertBalance(icaAddr, expBalAfterSecondSend)
}
}
// assertBalance asserts that the provided address has exactly the expected balance.
// CONTRACT: the expected balance must only contain one coin denom.
func (suite *InterchainAccountsTestSuite) assertBalance(addr sdk.AccAddress, expBalance sdk.Coins) {
balance := suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), addr, sdk.DefaultBondDenom)
suite.Require().Equal(expBalance[0], balance)
}
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: "",
}
// Context, port identifier and channel identifier are unused for host.
icaHostModule := icahost.NewIBCModule(suite.chainA.GetSimApp().ICAHostKeeper)
packetData, version, err := icaHostModule.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 unused for host.
packetData, version, err = icaHostModule.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, invalidPacketData)
suite.Require().Error(err)
suite.Require().Empty(version)
suite.Require().Nil(packetData)
}
}