package middleware_test import ( "math/rand" "testing" "time" "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/simapp/helpers" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/simulation" txtypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/middleware" authsign "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/bank/testutil" "github.com/cosmos/cosmos-sdk/x/feegrant" ) func (s *MWTestSuite) TestDeductFeesNoDelegation() { ctx := s.SetupTest(false) // setup app := s.app protoTxCfg := tx.NewTxConfig(codec.NewProtoCodec(app.InterfaceRegistry()), tx.DefaultSignModes) txHandler := middleware.ComposeMiddlewares( noopTxHandler, middleware.DeductFeeMiddleware( s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, ), ) // keys and addresses priv1, _, addr1 := testdata.KeyTestPubAddr() priv2, _, addr2 := testdata.KeyTestPubAddr() priv3, _, addr3 := testdata.KeyTestPubAddr() priv4, _, addr4 := testdata.KeyTestPubAddr() priv5, _, addr5 := testdata.KeyTestPubAddr() // Set addr1 with insufficient funds err := testutil.FundAccount(s.app.BankKeeper, ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))}) s.Require().NoError(err) // Set addr2 with more funds err = testutil.FundAccount(s.app.BankKeeper, ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))}) s.Require().NoError(err) // grant fee allowance from `addr2` to `addr3` (plenty to pay) err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr3, &feegrant.BasicAllowance{ SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)), }) s.Require().NoError(err) // grant low fee allowance (20atom), to check the tx requesting more than allowed. err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr4, &feegrant.BasicAllowance{ SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 20)), }) s.Require().NoError(err) cases := map[string]struct { signerKey cryptotypes.PrivKey signer sdk.AccAddress feeAccount sdk.AccAddress fee int64 valid bool }{ "paying with low funds": { signerKey: priv1, signer: addr1, fee: 50, valid: false, }, "paying with good funds": { signerKey: priv2, signer: addr2, fee: 50, valid: true, }, "paying with no account": { signerKey: priv3, signer: addr3, fee: 1, valid: false, }, "no fee with real account": { signerKey: priv1, signer: addr1, fee: 0, valid: true, }, "no fee with no account": { signerKey: priv5, signer: addr5, fee: 0, valid: false, }, "valid fee grant without account": { signerKey: priv3, signer: addr3, feeAccount: addr2, fee: 50, valid: true, }, "no fee grant": { signerKey: priv3, signer: addr3, feeAccount: addr1, fee: 2, valid: false, }, "allowance smaller than requested fee": { signerKey: priv4, signer: addr4, feeAccount: addr2, fee: 50, valid: false, }, "granter cannot cover allowed fee grant": { signerKey: priv4, signer: addr4, feeAccount: addr1, fee: 50, valid: false, }, } for name, stc := range cases { tc := stc // to make scopelint happy s.T().Run(name, func(t *testing.T) { fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee)) msgs := []sdk.Msg{testdata.NewTestMsg(tc.signer)} acc := app.AccountKeeper.GetAccount(ctx, tc.signer) privs, accNums, seqs := []cryptotypes.PrivKey{tc.signerKey}, []uint64{0}, []uint64{0} if acc != nil { accNums, seqs = []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()} } testTx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, helpers.DefaultGenTxGas, ctx.ChainID(), accNums, seqs, tc.feeAccount, privs...) s.Require().NoError(err) // tests only feegrant middleware _, err = txHandler.DeliverTx(sdk.WrapSDKContext(ctx), txtypes.Request{Tx: testTx}) if tc.valid { s.Require().NoError(err) } else { s.Require().Error(err) } // tests while stack _, err = s.txHandler.DeliverTx(sdk.WrapSDKContext(ctx), txtypes.Request{Tx: testTx}) if tc.valid { s.Require().NoError(err) } else { s.Require().Error(err) } }) } } // don't consume any gas func SigGasNoConsumer(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params authtypes.Params) error { return nil } func genTxWithFeeGranter(gen client.TxConfig, msgs []sdk.Msg, feeAmt sdk.Coins, gas uint64, chainID string, accNums, accSeqs []uint64, feeGranter sdk.AccAddress, priv ...cryptotypes.PrivKey) (sdk.Tx, error) { sigs := make([]signing.SignatureV2, len(priv)) // create a random length memo r := rand.New(rand.NewSource(time.Now().UnixNano())) memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100)) signMode := gen.SignModeHandler().DefaultMode() // 1st round: set SignatureV2 with empty signatures, to set correct // signer infos. for i, p := range priv { sigs[i] = signing.SignatureV2{ PubKey: p.PubKey(), Data: &signing.SingleSignatureData{ SignMode: signMode, }, Sequence: accSeqs[i], } } tx := gen.NewTxBuilder() err := tx.SetMsgs(msgs...) if err != nil { return nil, err } err = tx.SetSignatures(sigs...) if err != nil { return nil, err } tx.SetMemo(memo) tx.SetFeeAmount(feeAmt) tx.SetGasLimit(gas) tx.SetFeeGranter(feeGranter) // 2nd round: once all signer infos are set, every signer can sign. for i, p := range priv { signerData := authsign.SignerData{ Address: sdk.AccAddress(p.PubKey().Address()).String(), ChainID: chainID, AccountNumber: accNums[i], Sequence: accSeqs[i], PubKey: p.PubKey(), } signBytes, err := gen.SignModeHandler().GetSignBytes(signMode, signerData, tx.GetTx()) if err != nil { panic(err) } sig, err := p.Sign(signBytes) if err != nil { panic(err) } sigs[i].Data.(*signing.SingleSignatureData).Signature = sig err = tx.SetSignatures(sigs...) if err != nil { panic(err) } } return tx.GetTx(), nil }