Skip to content

Commit

Permalink
Merge pull request from GHSA-832c-mq9v-367r
Browse files Browse the repository at this point in the history
* fix: use block app hash and tx list to generate interchain account address

Generate interchain account addresses using host connection ID, controller PortID, block app hash, and block data hash
Update tests to handle non-determinstic address creation
Add test case to ensure address generation is block dependent

* fix: return error on existing non-interchainaccounts for generated address

If an account exists and is not an interchain account return an error
Add test cases for existing accounts, both interchain and non interchain account
Refactor account tests to be table tests

* fix: refactor handshake code to account for block dependent address generation

* add more test cases, update error messaging

* self review fix

* increase test readability

* add test cases for controller side channel reopening

* chore: fix godoc
  • Loading branch information
colin-axner authored Sep 20, 2022
1 parent a1bd45a commit 140b31b
Show file tree
Hide file tree
Showing 16 changed files with 260 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/crypto"

"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/controller/types"
icatypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"
Expand All @@ -19,12 +18,6 @@ import (
)

var (
// TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module()
// https://github.com/cosmos/cosmos-sdk/issues/10225
//
// TestAccAddress defines a reusable bech32 address for testing purposes
TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), ibctesting.FirstConnectionID, TestPortID)

// TestOwnerAddress defines a reusable bech32 address for testing purposes
TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs"

Expand Down Expand Up @@ -490,7 +483,7 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() {
0,
)

ack := cbs.OnRecvPacket(suite.chainA.GetContext(), packet, TestAccAddress)
ack := cbs.OnRecvPacket(suite.chainA.GetContext(), packet, nil)
suite.Require().Equal(tc.expPass, ack.Success())
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func (suite *KeeperTestSuite) TestInitGenesis() {
suite.SetupTest()

interchainAccAddr := icatypes.GenerateAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, TestPortID)
genesisState := icatypes.ControllerGenesisState{
ActiveChannels: []icatypes.ActiveChannel{
{
Expand All @@ -22,7 +23,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() {
{
ConnectionId: ibctesting.FirstConnectionID,
PortId: TestPortID,
AccountAddress: TestAccAddress.String(),
AccountAddress: interchainAccAddr.String(),
},
},
Ports: []string{TestPortID},
Expand All @@ -36,7 +37,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() {

accountAdrr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID)
suite.Require().True(found)
suite.Require().Equal(TestAccAddress.String(), accountAdrr)
suite.Require().Equal(interchainAccAddr.String(), accountAdrr)

expParams := types.NewParams(false)
params := suite.chainA.GetSimApp().ICAControllerKeeper.GetParams(suite.chainA.GetContext())
Expand All @@ -52,12 +53,15 @@ func (suite *KeeperTestSuite) TestExportGenesis() {
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)

interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID)
suite.Require().True(exists)

genesisState := keeper.ExportGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAControllerKeeper)

suite.Require().Equal(path.EndpointA.ChannelID, genesisState.ActiveChannels[0].ChannelId)
suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.ActiveChannels[0].PortId)

suite.Require().Equal(TestAccAddress.String(), genesisState.InterchainAccounts[0].AccountAddress)
suite.Require().Equal(interchainAccAddr, genesisState.InterchainAccounts[0].AccountAddress)
suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.InterchainAccounts[0].PortId)

suite.Require().Equal([]string{TestPortID}, genesisState.GetPorts())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/controller/types"
icatypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"
ibctesting "github.com/cosmos/ibc-go/v5/testing"
)

Expand Down Expand Up @@ -64,10 +63,11 @@ func (suite *KeeperTestSuite) TestQueryInterchainAccount() {
res, err := suite.chainA.GetSimApp().ICAControllerKeeper.InterchainAccount(sdk.WrapSDKContext(suite.chainA.GetContext()), req)

if tc.expPass {
expAddress := icatypes.GenerateAddress(suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(icatypes.ModuleName), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID)
expAddress, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID)
suite.Require().True(exists)

suite.Require().NoError(err)
suite.Require().Equal(expAddress.String(), res.Address)
suite.Require().Equal(expAddress, res.Address)
} else {
suite.Require().Error(err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() {
}{
{
"success",
func() {
path.EndpointA.SetChannel(*channel)
},
func() {},
true,
},
{
Expand Down Expand Up @@ -54,30 +52,44 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() {
},
true,
},
{
"success: channel reopening",
func() {
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)

err = path.EndpointA.SetChannelClosed()
suite.Require().NoError(err)

err = path.EndpointB.SetChannelClosed()
suite.Require().NoError(err)

path.EndpointA.ChannelID = ""
path.EndpointB.ChannelID = ""
},
true,
},
{
"invalid metadata - previous metadata is different",
func() {
// set active channel to closed
suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)

// attempt to downgrade version by reinitializing channel with version 1, but setting channel to version 2
metadata.Version = "ics27-2"

versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata)
suite.Require().NoError(err)

counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
closedChannel := channeltypes.Channel{
State: channeltypes.CLOSED,
Ordering: channeltypes.ORDERED,
Counterparty: counterparty,
ConnectionHops: []string{path.EndpointA.ConnectionID},
Version: TestVersion,
Version: string(versionBytes),
}

path.EndpointA.SetChannel(closedChannel)

// modify metadata
metadata.Version = "ics27-2"

versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata)
suite.Require().NoError(err)

channel.Version = string(versionBytes)
},
false,
},
Expand Down Expand Up @@ -384,7 +396,10 @@ func (suite *KeeperTestSuite) TestOnChanOpenAck() {
err = path.EndpointB.ChanOpenTry()
suite.Require().NoError(err)

metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestAccAddress.String(), icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg)
interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID)
suite.Require().True(exists)

metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, interchainAccAddr, icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg)
versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata)
suite.Require().NoError(err)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,14 @@ package keeper_test
import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/crypto"

icatypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v5/testing"
)

var (
// TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module()
// https://github.com/cosmos/cosmos-sdk/issues/10225
//
// TestAccAddress defines a reusable bech32 address for testing purposes
TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), ibctesting.FirstConnectionID, TestPortID)

// TestOwnerAddress defines a reusable bech32 address for testing purposes
TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs"

Expand Down Expand Up @@ -152,11 +144,10 @@ func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() {
suite.Require().NoError(err)

counterpartyPortID := path.EndpointA.ChannelConfig.PortID
expectedAddr := icatypes.GenerateAddress(suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(icatypes.ModuleName), ibctesting.FirstConnectionID, counterpartyPortID)

retrievedAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, counterpartyPortID)
suite.Require().True(found)
suite.Require().Equal(expectedAddr.String(), retrievedAddr)
suite.Require().NotEmpty(retrievedAddr)

retrievedAddr, found = suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), "invalid conn", "invalid port")
suite.Require().False(found)
Expand Down Expand Up @@ -211,13 +202,16 @@ func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() {
err := SetupICAPath(path, TestOwnerAddress)
suite.Require().NoError(err)

interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID)
suite.Require().True(exists)

suite.chainA.GetSimApp().ICAControllerKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, expectedPortID, expectedAccAddr)

expectedAccounts := []icatypes.RegisteredInterchainAccount{
{
ConnectionId: ibctesting.FirstConnectionID,
PortId: TestPortID,
AccountAddress: TestAccAddress.String(),
AccountAddress: interchainAccAddr,
},
{
ConnectionId: ibctesting.FirstConnectionID,
Expand Down
59 changes: 39 additions & 20 deletions modules/apps/27-interchain-accounts/host/ibc_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/crypto"

"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/host/types"
icatypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"
Expand All @@ -22,12 +21,6 @@ import (
)

var (
// TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module()
// https://github.com/cosmos/cosmos-sdk/issues/10225
//
// TestAccAddress defines a reusable bech32 address for testing purposes
TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), ibctesting.FirstConnectionID, TestPortID)

// TestOwnerAddress defines a reusable bech32 address for testing purposes
TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs"

Expand Down Expand Up @@ -148,6 +141,17 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() {
{
"success", func() {}, true,
},
{
"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", sdk.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()
}, true,
},
{
"host submodule disabled", func() {
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{}))
Expand Down Expand Up @@ -214,6 +218,10 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() {

if tc.expPass {
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().Error(err)
suite.Require().Equal("", version)
Expand Down Expand Up @@ -527,7 +535,7 @@ func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() {
0,
)

err = cbs.OnAcknowledgementPacket(suite.chainB.GetContext(), packet, []byte("ackBytes"), TestAccAddress)
err = cbs.OnAcknowledgementPacket(suite.chainB.GetContext(), packet, []byte("ackBytes"), nil)

if tc.expPass {
suite.Require().NoError(err)
Expand Down Expand Up @@ -580,7 +588,7 @@ func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() {
0,
)

err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), packet, TestAccAddress)
err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), packet, nil)

if tc.expPass {
suite.Require().NoError(err)
Expand All @@ -606,21 +614,28 @@ func (suite *InterchainAccountsTestSuite) fundICAWallet(ctx sdk.Context, portID
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
// by opening a new channel on the associated portID
// 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() {
// create channel + init interchain account on a particular port
path := NewICAPath(suite.chainA, suite.chainB)
suite.coordinator.SetupConnections(path)

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, sdk.NewInt(100000)))
tokenAmt = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.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, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000))))
interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID)
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)

tokenAmt := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(5000)))
msg := &banktypes.MsgSend{
FromAddress: interchainAccountAddr,
ToAddress: suite.chainB.SenderAccount.GetAddress().String(),
Expand Down Expand Up @@ -654,8 +669,7 @@ func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose()
icaAddr, err := sdk.AccAddressFromBech32(interchainAccountAddr)
suite.Require().NoError(err)

hasBalance := suite.chainB.GetSimApp().BankKeeper.HasBalance(suite.chainB.GetContext(), icaAddr, sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(5000)})
suite.Require().True(hasBalance)
suite.assertBalance(icaAddr, expBalAfterFirstSend)

// close the channel
err = path.EndpointA.SetChannelClosed()
Expand All @@ -681,7 +695,12 @@ func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose()
err = path.RelayPacket(packetRelay)
suite.Require().NoError(err) // relay committed

// check that the ica balance is updated
hasBalance = suite.chainB.GetSimApp().BankKeeper.HasBalance(suite.chainB.GetContext(), icaAddr, sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(0)})
suite.Require().True(hasBalance)
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)
}
15 changes: 10 additions & 5 deletions modules/apps/27-interchain-accounts/host/keeper/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

icatypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"
)

// RegisterInterchainAccount attempts to create a new account using the provided address and
// stores it in state keyed by the provided connection and port identifiers
// If an account for the provided address already exists this function returns early (no-op)
func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, controllerPortID string, accAddress sdk.AccAddress) {
// createInterchainAccount creates a new interchain account. An address is generated using the host connectionID, the controller portID,
// and block dependent information. An error is returned if an account already exists for the generated account.
// An interchain account type is set in the account keeper and the interchain account address mapping is updated.
func (k Keeper) createInterchainAccount(ctx sdk.Context, connectionID, controllerPortID string) (sdk.AccAddress, error) {
accAddress := icatypes.GenerateAddress(ctx, connectionID, controllerPortID)

if acc := k.accountKeeper.GetAccount(ctx, accAddress); acc != nil {
return
return nil, sdkerrors.Wrapf(icatypes.ErrAccountAlreadyExist, "existing account for newly generated interchain account address %s", accAddress)
}

interchainAccount := icatypes.NewInterchainAccount(
Expand All @@ -24,4 +27,6 @@ func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, control
k.accountKeeper.SetAccount(ctx, interchainAccount)

k.SetInterchainAccountAddress(ctx, connectionID, controllerPortID, interchainAccount.Address)

return accAddress, nil
}
Loading

0 comments on commit 140b31b

Please sign in to comment.