diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f9200953d2..7dc23f363cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Testing * [\#7430](https://github.com/cosmos/ibc-go/pull/7430) Update the block proposer in test chains for each block. +* [\#7688](https://github.com/cosmos/ibc-go/pull/7688) Added `SendMsgsWithSender` to `TestChain`. ### Dependencies diff --git a/modules/apps/transfer/v2/keeper/msg_server_test.go b/modules/apps/transfer/v2/keeper/msg_server_test.go index 2288ee34b2c..076aca171f8 100644 --- a/modules/apps/transfer/v2/keeper/msg_server_test.go +++ b/modules/apps/transfer/v2/keeper/msg_server_test.go @@ -9,7 +9,10 @@ import ( sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" transfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" @@ -22,8 +25,12 @@ import ( // TestMsgSendPacketTransfer tests the MsgSendPacket rpc handler for the transfer v2 application. func (suite *KeeperTestSuite) TestMsgSendPacketTransfer() { - var payload channeltypesv2.Payload - var path *ibctesting.Path + var ( + payload channeltypesv2.Payload + path *ibctesting.Path + expEscrowAmounts []transfertypes.Token // total amounts in escrow for each token + sender ibctesting.SenderAccount + ) testCases := []struct { name string @@ -60,6 +67,94 @@ func (suite *KeeperTestSuite) TestMsgSendPacketTransfer() { }, nil, }, + { + "successful transfer of entire spendable balance with vesting account", + func() { + // create vesting account + vestingAccPrivKey := secp256k1.GenPrivKey() + vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address()) + + vestingCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, ibctesting.DefaultCoinAmount)) + _, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount( + suite.chainA.SenderAccount.GetAddress(), + vestingAccAddress, + vestingCoins, + suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(), + false, + )) + suite.Require().NoError(err) + + // transfer some spendable coins to vesting account + spendableAmount := sdkmath.NewInt(42) + transferCoins := sdk.NewCoins(sdk.NewCoin(vestingCoins[0].Denom, spendableAmount)) + _, err = suite.chainA.SendMsgs(banktypes.NewMsgSend(suite.chainA.SenderAccount.GetAddress(), vestingAccAddress, transferCoins)) + suite.Require().NoError(err) + + // just to prove that the vesting account has a balance (but only spendableAmount is spendable) + vestingAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), vestingAccAddress, vestingCoins[0].Denom) + suite.Require().Equal(vestingCoins[0].Amount.Uint64()+spendableAmount.Uint64(), vestingAccBalance.Amount.Uint64()) + vestinSpendableBalance := suite.chainA.GetSimApp().BankKeeper.SpendableCoins(suite.chainA.GetContext(), vestingAccAddress) + suite.Require().Equal(spendableAmount.Uint64(), vestinSpendableBalance.AmountOf(vestingCoins[0].Denom).Uint64()) + + bz, err := ics20lib.EncodeFungibleTokenPacketData(ics20lib.ICS20LibFungibleTokenPacketData{ + Denom: sdk.DefaultBondDenom, + Amount: transfertypes.UnboundedSpendLimit().BigInt(), + Sender: vestingAccAddress.String(), + Receiver: suite.chainB.SenderAccount.GetAddress().String(), + Memo: "", + }) + suite.Require().NoError(err) + payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingABI, bz) + + sender = suite.chainA.GetSenderAccount(vestingAccPrivKey) + + expEscrowAmounts = []transfertypes.Token{ + { + Denom: transfertypes.NewDenom(sdk.DefaultBondDenom), + Amount: spendableAmount.String(), // The only spendable amount + }, + } + }, + nil, + }, + { + "failure: no spendable coins for vesting account", + func() { + // create vesting account + vestingAccPrivKey := secp256k1.GenPrivKey() + vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address()) + + vestingCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, ibctesting.DefaultCoinAmount)) + _, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount( + suite.chainA.SenderAccount.GetAddress(), + vestingAccAddress, + vestingCoins, + suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(), + false, + )) + suite.Require().NoError(err) + + // just to prove that the vesting account has a balance (but not spendable) + vestingAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), vestingAccAddress, vestingCoins[0].Denom) + suite.Require().Equal(vestingCoins[0].Amount.Uint64(), vestingAccBalance.Amount.Uint64()) + vestinSpendableBalance := suite.chainA.GetSimApp().BankKeeper.SpendableCoins(suite.chainA.GetContext(), vestingAccAddress) + suite.Require().Zero(vestinSpendableBalance.AmountOf(vestingCoins[0].Denom).Uint64()) + + // try to transfer the entire spendable balance (which is zero) + bz, err := ics20lib.EncodeFungibleTokenPacketData(ics20lib.ICS20LibFungibleTokenPacketData{ + Denom: sdk.DefaultBondDenom, + Amount: transfertypes.UnboundedSpendLimit().BigInt(), + Sender: vestingAccAddress.String(), + Receiver: suite.chainB.SenderAccount.GetAddress().String(), + Memo: "", + }) + suite.Require().NoError(err) + payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V1, transfertypes.EncodingABI, bz) + + sender = suite.chainA.GetSenderAccount(vestingAccPrivKey) + }, + transfertypes.ErrInvalidAmount, + }, { "failure: send transfers disabled", func() { @@ -91,6 +186,8 @@ func (suite *KeeperTestSuite) TestMsgSendPacketTransfer() { Amount: ibctesting.DefaultCoinAmount.String(), }, } + expEscrowAmounts = tokens + sender = suite.chainA.SenderAccounts[0] ftpd := transfertypes.NewFungibleTokenPacketDataV2(tokens, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "", transfertypes.ForwardingPacketData{}) bz := suite.chainA.Codec.MustMarshal(&ftpd) @@ -99,8 +196,7 @@ func (suite *KeeperTestSuite) TestMsgSendPacketTransfer() { payload = channeltypesv2.NewPayload(transfertypes.ModuleName, transfertypes.ModuleName, transfertypes.V2, transfertypes.EncodingProtobuf, bz) tc.malleate() - - packet, err := path.EndpointA.MsgSendPacket(timestamp, payload) + packet, err := path.EndpointA.MsgSendPacketWithSender(timestamp, payload, sender) expPass := tc.expError == nil if expPass { @@ -108,9 +204,9 @@ func (suite *KeeperTestSuite) TestMsgSendPacketTransfer() { suite.Require().NotEmpty(packet) // ensure every token sent is escrowed. - for _, t := range tokens { + for i, t := range tokens { escrowedAmount := suite.chainA.GetSimApp().TransferKeeperV2.GetTotalEscrowForDenom(suite.chainA.GetContext(), t.Denom.IBCDenom()) - expected, err := t.ToCoin() + expected, err := expEscrowAmounts[i].ToCoin() suite.Require().NoError(err) suite.Require().Equal(expected, escrowedAmount, "escrowed amount is not equal to expected amount") } diff --git a/testing/chain.go b/testing/chain.go index 67ff41baf9e..54ebc978c4c 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -346,10 +346,20 @@ func (chain *TestChain) sendMsgs(msgs ...sdk.Msg) error { return err } -// SendMsgs delivers a transaction through the application. It updates the senders sequence -// number and updates the TestChain's headers. It returns the result and error if one -// occurred. +// SendMsgs delivers a transaction through the application using a predefined sender. +// It updates the senders sequence number and updates the TestChain's headers. +// It returns the result and error if one occurred. func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*abci.ExecTxResult, error) { + senderAccount := SenderAccount{ + SenderPrivKey: chain.SenderPrivKey, + SenderAccount: chain.SenderAccount, + } + + return chain.SendMsgsWithSender(senderAccount, msgs...) +} + +// SendMsgsWithSender delivers a transaction through the application using the provided sender. +func (chain *TestChain) SendMsgsWithSender(sender SenderAccount, msgs ...sdk.Msg) (*abci.ExecTxResult, error) { if chain.SendMsgsOverride != nil { return chain.SendMsgsOverride(msgs...) } @@ -359,7 +369,7 @@ func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*abci.ExecTxResult, error) { // increment acc sequence regardless of success or failure tx execution defer func() { - err := chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) + err := sender.SenderAccount.SetSequence(sender.SenderAccount.GetSequence() + 1) if err != nil { panic(err) } @@ -371,12 +381,12 @@ func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*abci.ExecTxResult, error) { chain.App.GetBaseApp(), msgs, chain.ChainID, - []uint64{chain.SenderAccount.GetAccountNumber()}, - []uint64{chain.SenderAccount.GetSequence()}, + []uint64{sender.SenderAccount.GetAccountNumber()}, + []uint64{sender.SenderAccount.GetSequence()}, true, chain.ProposedHeader.GetTime(), chain.NextVals.Hash(), - chain.SenderPrivKey, + sender.SenderPrivKey, ) if err != nil { return nil, err @@ -593,3 +603,13 @@ func (chain *TestChain) IBCClientHeader(header *ibctm.Header, trustedHeight clie return header, nil } + +// GetSenderAccount returns the sender account associated with the provided private key. +func (chain *TestChain) GetSenderAccount(privKey cryptotypes.PrivKey) SenderAccount { + account := chain.GetSimApp().AccountKeeper.GetAccount(chain.GetContext(), sdk.AccAddress(privKey.PubKey().Address())) + + return SenderAccount{ + SenderPrivKey: privKey, + SenderAccount: account, + } +} diff --git a/testing/endpoint_v2.go b/testing/endpoint_v2.go index e28ff51fb6b..be1fd011aa8 100644 --- a/testing/endpoint_v2.go +++ b/testing/endpoint_v2.go @@ -38,11 +38,21 @@ func (endpoint *Endpoint) RegisterCounterparty() (err error) { return err } -// MsgSendPacket sends a packet on the associated endpoint. The constructed packet is returned. +// MsgSendPacket sends a packet on the associated endpoint using a predefined sender. The constructed packet is returned. func (endpoint *Endpoint) MsgSendPacket(timeoutTimestamp uint64, payload channeltypesv2.Payload) (channeltypesv2.Packet, error) { - msgSendPacket := channeltypesv2.NewMsgSendPacket(endpoint.ChannelID, timeoutTimestamp, endpoint.Chain.SenderAccount.GetAddress().String(), payload) + senderAccount := SenderAccount{ + SenderPrivKey: endpoint.Chain.SenderPrivKey, + SenderAccount: endpoint.Chain.SenderAccount, + } + + return endpoint.MsgSendPacketWithSender(timeoutTimestamp, payload, senderAccount) +} + +// MsgSendPacketWithSender sends a packet on the associated endpoint using the provided sender. The constructed packet is returned. +func (endpoint *Endpoint) MsgSendPacketWithSender(timeoutTimestamp uint64, payload channeltypesv2.Payload, sender SenderAccount) (channeltypesv2.Packet, error) { + msgSendPacket := channeltypesv2.NewMsgSendPacket(endpoint.ChannelID, timeoutTimestamp, sender.SenderAccount.GetAddress().String(), payload) - res, err := endpoint.Chain.SendMsgs(msgSendPacket) + res, err := endpoint.Chain.SendMsgsWithSender(sender, msgSendPacket) if err != nil { return channeltypesv2.Packet{}, err }