Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: adding fee middleware tests for ics27 interchain accounts #1433

Merged
merged 17 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions modules/apps/27-interchain-accounts/types/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ func NewMetadata(version, controllerConnectionID, hostConnectionID, accAddress,
}
}

// NewDefaultMetadataString creates and returns a new JSON encoded version string containing the default ICS27 Metadata values
// with the provided controller and host connection identifiers
func NewDefaultMetadataString(controllerConnectionID, hostConnectionID string) string {
metadata := Metadata{
Version: Version,
ControllerConnectionId: controllerConnectionID,
HostConnectionId: hostConnectionID,
Encoding: EncodingProtobuf,
TxType: TxTypeSDKMultiMsg,
}

return string(ModuleCdc.MustMarshalJSON(&metadata))
}

// IsPreviousMetadataEqual compares a metadata to a previous version string set in a channel struct.
// It ensures all fields are equal except the Address string
func IsPreviousMetadataEqual(previousVersion string, metadata Metadata) bool {
Expand Down
197 changes: 197 additions & 0 deletions modules/apps/29-fee/ica_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package fee_test

import (
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

icahosttypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types"
icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types"
"github.com/cosmos/ibc-go/v3/modules/apps/29-fee/types"
clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v3/testing"
)

var (
// defaultOwnerAddress defines a reusable bech32 address for testing purposes
defaultOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs"

// defaultPortID defines a resuable port identifier for testing purposes
defaultPortID, _ = icatypes.NewControllerPortID(defaultOwnerAddress)

// defaultICAVersion defines a resuable interchainaccounts version string for testing purposes
defaultICAVersion = icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID)
)

// NewIncentivizedICAPath creates and returns a new ibctesting path configured for a fee enabled interchain accounts channel
func NewIncentivizedICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path {
path := ibctesting.NewPath(chainA, chainB)

feeMetadata := types.Metadata{
FeeVersion: types.Version,
AppVersion: defaultICAVersion,
}

feeICAVersion := string(types.ModuleCdc.MustMarshalJSON(&feeMetadata))

path.SetChannelOrdered()
path.EndpointA.ChannelConfig.Version = feeICAVersion
path.EndpointB.ChannelConfig.Version = feeICAVersion
path.EndpointA.ChannelConfig.PortID = defaultPortID
path.EndpointB.ChannelConfig.PortID = icatypes.PortID

return path
}

// SetupPath performs the InterchainAccounts channel creation handshake using an ibctesting path
func SetupPath(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
}

if err := path.EndpointB.ChanOpenConfirm(); err != nil {
return err
}

return nil
}

// RegisterInterchainAccount invokes the the InterchainAccounts entrypoint, routes a new MsgChannelOpenInit to the appropriate handler,
// commits state changes and updates the testing endpoint accordingly
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); 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
}

// TestFeeInterchainAccounts Integration test to ensure ics29 works with ics27
func (suite *FeeTestSuite) TestFeeInterchainAccounts() {
path := NewIncentivizedICAPath(suite.chainA, suite.chainB)
suite.coordinator.SetupConnections(path)

err := SetupPath(path, defaultOwnerAddress)
suite.Require().NoError(err)

// assert the newly established channel is fee enabled on both ends
suite.Require().True(suite.chainA.GetSimApp().IBCFeeKeeper.IsFeeEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID))
suite.Require().True(suite.chainB.GetSimApp().IBCFeeKeeper.IsFeeEnabled(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID))

// register counterparty address on destination chainB as chainA.SenderAccounts[1] for recv fee distribution
suite.chainB.GetSimApp().IBCFeeKeeper.SetCounterpartyAddress(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccounts[1].SenderAccount.GetAddress().String(), path.EndpointB.ChannelID)

// escrow a packet fee for the next send sequence
expectedFee := types.NewFee(defaultRecvFee, defaultAckFee, defaultTimeoutFee)
msgPayPacketFee := types.NewMsgPayPacketFee(expectedFee, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, suite.chainA.SenderAccount.GetAddress().String(), nil)

// fetch the account balance before fees are escrowed and assert the difference below
preEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom)

res, err := suite.chainA.SendMsgs(msgPayPacketFee)
suite.Require().NotNil(res)
suite.Require().NoError(err)

postEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom)
suite.Require().Equal(postEscrowBalance.AddAmount(expectedFee.Total().AmountOf(sdk.DefaultBondDenom)), preEscrowBalance)

packetID := channeltypes.NewPacketId(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1)
packetFees, found := suite.chainA.GetSimApp().IBCFeeKeeper.GetFeesInEscrow(suite.chainA.GetContext(), packetID)
suite.Require().True(found)
suite.Require().Equal(expectedFee, packetFees.PacketFees[0].Fee)

interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID)
suite.Require().True(found)

// fund the interchain account on chainB
coins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000)))
msgBankSend := &banktypes.MsgSend{
FromAddress: suite.chainB.SenderAccount.GetAddress().String(),
ToAddress: interchainAccountAddr,
Amount: coins,
}

res, err = suite.chainB.SendMsgs(msgBankSend)
suite.Require().NotEmpty(res)
suite.Require().NoError(err)

// prepare a simple stakingtypes.MsgDelegate to be used as the interchain account msg executed on chainB
validatorAddr := (sdk.ValAddress)(suite.chainB.Vals.Validators[0].Address)
msgDelegate := &stakingtypes.MsgDelegate{
DelegatorAddress: interchainAccountAddr,
ValidatorAddress: validatorAddr.String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(5000)),
}

data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []sdk.Msg{msgDelegate})
suite.Require().NoError(err)

icaPacketData := icatypes.InterchainAccountPacketData{
Type: icatypes.EXECUTE_TX,
Data: data,
}

// ensure chainB is allowed to execute stakingtypes.MsgDelegate
params := icahosttypes.NewParams(true, []string{sdk.MsgTypeURL(msgDelegate)})
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params)

// build the interchain accounts packet
packet := buildInterchainAccountsPacket(path, icaPacketData.GetBytes(), 1)

// write packet commitment to state on chainA and commit state
commitment := channeltypes.CommitPacket(suite.chainA.GetSimApp().AppCodec(), packet)
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, commitment)
suite.chainA.NextBlock()

err = path.RelayPacket(packet)
suite.Require().NoError(err)

// ensure escrowed fees are cleaned up
packetFees, found = suite.chainA.GetSimApp().IBCFeeKeeper.GetFeesInEscrow(suite.chainA.GetContext(), packetID)
suite.Require().False(found)
suite.Require().Empty(packetFees)

// assert the value of the account balance after fee distribution
// NOTE: the balance after fee distribution should be equal to the pre-escrow balance minus the recv fee
// as chainA.SenderAccount is used as the msg signer and refund address for msgPayPacketFee above as well as the relyer account for acknowledgements in path.RelayPacket()
postDistBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom)
suite.Require().Equal(preEscrowBalance.SubAmount(defaultRecvFee.AmountOf(sdk.DefaultBondDenom)), postDistBalance)
}

func buildInterchainAccountsPacket(path *ibctesting.Path, data []byte, seq uint64) channeltypes.Packet {
packet := channeltypes.NewPacket(
data,
1,
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
path.EndpointB.ChannelConfig.PortID,
path.EndpointB.ChannelID,
clienttypes.NewHeight(0, 100),
0,
)

return packet
}