diff --git a/e2e/fee_middleware_test.go b/e2e/fee_middleware_test.go index a6532a8fd1f..f8c05822999 100644 --- a/e2e/fee_middleware_test.go +++ b/e2e/fee_middleware_test.go @@ -8,9 +8,11 @@ import ( "github.com/strangelove-ventures/ibctest/broadcast" "github.com/strangelove-ventures/ibctest/chain/cosmos" "github.com/strangelove-ventures/ibctest/ibc" + "github.com/strangelove-ventures/ibctest/test" "github.com/stretchr/testify/suite" "e2e/testsuite" + "e2e/testvalues" feetypes "github.com/cosmos/ibc-go/v4/modules/apps/29-fee/types" channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" @@ -75,11 +77,124 @@ func (s *FeeMiddlewareTestSuite) QueryIncentivizedPacketsForChannel( return res.IncentivizedPackets, err } -func (s *FeeMiddlewareTestSuite) TestPlaceholder() { - ctx := context.Background() - r := s.SetupChainsRelayerAndChannel(ctx, feeMiddlewareChannelOptions()) - s.T().Run("start relayer", func(t *testing.T) { - s.StartRelayer(r) +func (s *FeeMiddlewareTestSuite) TestMsgPayPacketFeeAsyncSingleSender() { + t := s.T() + ctx := context.TODO() + + relayer, channelA := s.SetupChainsRelayerAndChannel(ctx, feeMiddlewareChannelOptions()) + chainA, chainB := s.GetChains() + + var ( + chainADenom = chainA.Config().Denom + testFee = testvalues.DefaultFee(chainADenom) + chainATx ibc.Tx + payPacketFeeTxResp sdk.TxResponse + ) + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + t.Run("relayer wallets recovered", func(t *testing.T) { + err := s.RecoverRelayerWallets(ctx, relayer) + s.Require().NoError(err) + }) + + chainARelayerWallet, chainBRelayerWallet, err := s.GetRelayerWallets(relayer) + t.Run("relayer wallets fetched", func(t *testing.T) { + s.Require().NoError(err) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + _, chainBRelayerUser := s.GetRelayerUsers(ctx) + + t.Run("register counter party payee", func(t *testing.T) { + resp, err := s.RegisterCounterPartyPayee(ctx, chainB, chainBRelayerUser, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, chainBRelayerWallet.Address, chainARelayerWallet.Address) + s.Require().NoError(err) + s.AssertValidTxResponse(resp) + }) + + t.Run("verify counter party payee", func(t *testing.T) { + address, err := s.QueryCounterPartyPayee(ctx, chainB, chainBRelayerWallet.Address, channelA.Counterparty.ChannelID) + s.Require().NoError(err) + s.Require().Equal(chainARelayerWallet.Address, address) + }) + + walletAmount := ibc.WalletAmount{ + Address: chainAWallet.Bech32Address(chainB.Config().Bech32Prefix), // destination address + Denom: chainADenom, + Amount: testvalues.IBCTransferAmount, + } + + t.Run("send IBC transfer", func(t *testing.T) { + chainATx, err = chainA.SendIBCTransfer(ctx, channelA.ChannelID, chainAWallet.KeyName, walletAmount, nil) + s.Require().NoError(err) + s.Require().NoError(chainATx.Validate(), "chain-a ibc transfer tx is invalid") + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - walletAmount.Amount - chainA.GetGasFeesInNativeDenom(chainATx.GasSpent) + s.Require().Equal(expected, actualBalance) + }) + + t.Run("pay packet fee", func(t *testing.T) { + t.Run("no incentivized packets", func(t *testing.T) { + packets, err := s.QueryIncentivizedPacketsForChannel(ctx, chainA, channelA.PortID, channelA.ChannelID) + s.Require().NoError(err) + s.Require().Empty(packets) + }) + + packetId := channeltypes.NewPacketId(channelA.PortID, channelA.ChannelID, 1) + packetFee := feetypes.NewPacketFee(testFee, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), nil) + + t.Run("should succeed", func(t *testing.T) { + payPacketFeeTxResp, err = s.PayPacketFeeAsync(ctx, chainA, chainAWallet, packetId, packetFee) + s.Require().NoError(err) + s.AssertValidTxResponse(payPacketFeeTxResp) + }) + + t.Run("there should be incentivized packets", func(t *testing.T) { + packets, err := s.QueryIncentivizedPacketsForChannel(ctx, chainA, channelA.PortID, channelA.ChannelID) + s.Require().NoError(err) + s.Require().Len(packets, 1) + actualFee := packets[0].PacketFees[0].Fee + + s.Require().True(actualFee.RecvFee.IsEqual(testFee.RecvFee)) + s.Require().True(actualFee.AckFee.IsEqual(testFee.AckFee)) + s.Require().True(actualFee.TimeoutFee.IsEqual(testFee.TimeoutFee)) + }) + + t.Run("balance should be lowered by sum of recv ack and timeout", func(t *testing.T) { + // The balance should be lowered by the sum of the recv, ack and timeout fees. + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + gasFees := chainA.GetGasFeesInNativeDenom(chainATx.GasSpent) + chainA.GetGasFeesInNativeDenom(payPacketFeeTxResp.GasWanted) + expected := testvalues.StartingTokenAmount - walletAmount.Amount - gasFees - testFee.Total().AmountOf(chainADenom).Int64() + s.Require().Equal(expected, actualBalance) + }) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer) + }) + + t.Run("packets are relayed", func(t *testing.T) { + packets, err := s.QueryIncentivizedPacketsForChannel(ctx, chainA, channelA.PortID, channelA.ChannelID) + s.Require().NoError(err) + s.Require().Empty(packets) + }) + + t.Run("timeout fee is refunded", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + gasFees := chainA.GetGasFeesInNativeDenom(chainATx.GasSpent) + chainA.GetGasFeesInNativeDenom(payPacketFeeTxResp.GasWanted) + // once the relayer has relayed the packets, the timeout fee should be refunded. + expected := testvalues.StartingTokenAmount - walletAmount.Amount - gasFees - testFee.AckFee.AmountOf(chainADenom).Int64() - testFee.RecvFee.AmountOf(chainADenom).Int64() + s.Require().Equal(expected, actualBalance) }) } diff --git a/e2e/go.mod b/e2e/go.mod index 5b80823e0f3..feb8f7f43c1 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -5,10 +5,13 @@ go 1.18 replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 require ( + github.com/cosmos/cosmos-sdk v0.45.6 + github.com/cosmos/ibc-go/v4 v4.0.0-rc0 github.com/docker/docker v20.10.17+incompatible github.com/strangelove-ventures/ibctest v0.0.0-20220713213153-930886d8db30 github.com/stretchr/testify v1.8.0 go.uber.org/zap v1.21.0 + google.golang.org/grpc v1.47.0 ) require ( @@ -27,10 +30,8 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/confio/ics23/go v0.7.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect - github.com/cosmos/cosmos-sdk v0.45.6 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/iavl v0.17.3 // indirect - github.com/cosmos/ibc-go/v4 v4.0.0-rc0 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect github.com/danieljoos/wincred v1.0.2 // indirect @@ -124,7 +125,6 @@ require ( golang.org/x/tools v0.1.10 // indirect golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect - google.golang.org/grpc v1.47.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index d8b5387b305..7874f956639 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -65,11 +65,33 @@ func newPath(chainA, chainB *cosmos.CosmosChain) path { } } +// GetRelayerUsers returns two ibctest.User instances which can be used for the relayer users +// on the two chains. +func (s *E2ETestSuite) GetRelayerUsers(ctx context.Context, chainOpts ...testconfig.ChainOptionConfiguration) (*ibctest.User, *ibctest.User) { + chainA, chainB := s.GetChains(chainOpts...) + chainAAccountBytes, err := chainA.GetAddress(ctx, ChainARelayerName) + s.Require().NoError(err) + + chainBAccountBytes, err := chainB.GetAddress(ctx, ChainBRelayerName) + s.Require().NoError(err) + + chainARelayerUser := ibctest.User{ + Address: chainAAccountBytes, + KeyName: ChainARelayerName, + } + + chainBRelayerUser := ibctest.User{ + Address: chainBAccountBytes, + KeyName: ChainBRelayerName, + } + return &chainARelayerUser, &chainBRelayerUser +} + // SetupChainsRelayerAndChannel create two chains, a relayer, establishes a connection and creates a channel // using the given channel options. The relayer returned by this function has not yet started. It should be started // with E2ETestSuite.StartRelayer if needed. // This should be called at the start of every test, unless fine grained control is required. -func (s *E2ETestSuite) SetupChainsRelayerAndChannel(ctx context.Context, channelOpts ...func(*ibc.CreateChannelOptions)) ibc.Relayer { +func (s *E2ETestSuite) SetupChainsRelayerAndChannel(ctx context.Context, channelOpts ...func(*ibc.CreateChannelOptions)) (ibc.Relayer, ibc.ChannelOutput) { chainA, chainB := s.GetChains() home, err := ioutil.TempDir("", "") s.Require().NoError(err) @@ -121,7 +143,9 @@ func (s *E2ETestSuite) SetupChainsRelayerAndChannel(ctx context.Context, channel s.initGRPCClients(chainA) s.initGRPCClients(chainB) - return r + chainAChannels, err := r.GetChannels(ctx, eRep, chainA.Config().ChainID) + s.Require().NoError(err) + return r, chainAChannels[len(chainAChannels)-1] } // GetChains returns two chains that can be used in a test. The pair returned @@ -260,6 +284,16 @@ func (s *E2ETestSuite) initGRPCClients(chain *cosmos.CosmosChain) { } } +// AssertValidTxResponse verifies that an sdk.TxResponse +// has non-empty values. +func (s *E2ETestSuite) AssertValidTxResponse(resp sdk.TxResponse) { + respLogsMsg := resp.Logs.String() + s.Require().NotEqual(int64(0), resp.GasUsed, respLogsMsg) + s.Require().NotEqual(int64(0), resp.GasWanted, respLogsMsg) + s.Require().NotEmpty(resp.Events, respLogsMsg) + s.Require().NotEmpty(resp.Data, respLogsMsg) +} + // createCosmosChains creates two separate chains in docker containers. // test and can be retrieved with GetChains. func (s *E2ETestSuite) createCosmosChains(chainOptions testconfig.ChainOptions) (*cosmos.CosmosChain, *cosmos.CosmosChain) { diff --git a/e2e/testvalues/values.go b/e2e/testvalues/values.go new file mode 100644 index 00000000000..b91cfae1b84 --- /dev/null +++ b/e2e/testvalues/values.go @@ -0,0 +1,20 @@ +package testvalues + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + feetypes "github.com/cosmos/ibc-go/v4/modules/apps/29-fee/types" +) + +const ( + StartingTokenAmount int64 = 10_000_000 + IBCTransferAmount int64 = 10_000 +) + +func DefaultFee(denom string) feetypes.Fee { + return feetypes.Fee{ + RecvFee: sdk.NewCoins(sdk.NewCoin(denom, sdk.NewInt(50))), + AckFee: sdk.NewCoins(sdk.NewCoin(denom, sdk.NewInt(25))), + TimeoutFee: sdk.NewCoins(sdk.NewCoin(denom, sdk.NewInt(10))), + } +}