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
755 lines
27 KiB
Go
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)
|
|
}
|
|
}
|