266 lines
8.8 KiB
Go
266 lines
8.8 KiB
Go
package ante_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
|
|
txsigning "cosmossdk.io/x/tx/signing"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/types/simulation"
|
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
|
authsign "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/testutil"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/tx"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
)
|
|
|
|
func TestDeductFeesNoDelegation(t *testing.T) {
|
|
cases := map[string]struct {
|
|
fee int64
|
|
valid bool
|
|
err error
|
|
errMsg string
|
|
malleate func(*AnteTestSuite) (signer TestAccount, feeAcc sdk.AccAddress)
|
|
}{
|
|
"paying with low funds": {
|
|
fee: 50,
|
|
valid: false,
|
|
err: sdkerrors.ErrInsufficientFunds,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
accs := suite.CreateTestAccounts(1)
|
|
// 2 calls are needed because we run the ante twice
|
|
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(sdkerrors.ErrInsufficientFunds).Times(2)
|
|
return accs[0], nil
|
|
},
|
|
},
|
|
"paying with good funds": {
|
|
fee: 50,
|
|
valid: true,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
accs := suite.CreateTestAccounts(1)
|
|
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(nil).Times(2)
|
|
return accs[0], nil
|
|
},
|
|
},
|
|
"paying with no account": {
|
|
fee: 1,
|
|
valid: true,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
// Do not register the account
|
|
priv, _, addr := testdata.KeyTestPubAddr()
|
|
acc := authtypes.NewBaseAccountWithAddress(addr)
|
|
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(nil).Times(2)
|
|
return TestAccount{
|
|
acc: acc,
|
|
priv: priv,
|
|
}, nil
|
|
},
|
|
},
|
|
"no fee with real account": {
|
|
fee: 0,
|
|
valid: true,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
accs := suite.CreateTestAccounts(1)
|
|
return accs[0], nil
|
|
},
|
|
},
|
|
"no fee with no account": {
|
|
fee: 0,
|
|
valid: true,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
// Do not register the account
|
|
priv, _, addr := testdata.KeyTestPubAddr()
|
|
return TestAccount{
|
|
acc: authtypes.NewBaseAccountWithAddress(addr),
|
|
priv: priv,
|
|
}, nil
|
|
},
|
|
},
|
|
"valid fee grant": {
|
|
// note: the original test said "valid fee grant with no account".
|
|
// this is impossible given that feegrant.GrantAllowance calls
|
|
// SetAccount for the grantee.
|
|
fee: 50,
|
|
valid: true,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
accs := suite.CreateTestAccounts(2)
|
|
|
|
suite.feeGrantKeeper.EXPECT().UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil).Times(2)
|
|
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[1].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(nil).Times(2)
|
|
return accs[0], accs[1].acc.GetAddress()
|
|
},
|
|
},
|
|
"no fee grant": {
|
|
fee: 2,
|
|
valid: false,
|
|
err: sdkerrors.ErrNotFound,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
accs := suite.CreateTestAccounts(2)
|
|
suite.feeGrantKeeper.EXPECT().
|
|
UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).
|
|
Return(sdkerrors.ErrNotFound.Wrap("fee-grant not found")).
|
|
Times(2)
|
|
return accs[0], accs[1].acc.GetAddress()
|
|
},
|
|
},
|
|
"allowance smaller than requested fee": {
|
|
fee: 50,
|
|
valid: false,
|
|
errMsg: "fee limit exceeded",
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
accs := suite.CreateTestAccounts(2)
|
|
suite.feeGrantKeeper.EXPECT().
|
|
UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).
|
|
Return(errors.New("fee limit exceeded")).
|
|
Times(2)
|
|
return accs[0], accs[1].acc.GetAddress()
|
|
},
|
|
},
|
|
"granter cannot cover allowed fee grant": {
|
|
fee: 50,
|
|
valid: false,
|
|
err: sdkerrors.ErrInsufficientFunds,
|
|
malleate: func(suite *AnteTestSuite) (TestAccount, sdk.AccAddress) {
|
|
accs := suite.CreateTestAccounts(2)
|
|
suite.feeGrantKeeper.EXPECT().UseGrantedFees(gomock.Any(), accs[1].acc.GetAddress(), accs[0].acc.GetAddress(), gomock.Any(), gomock.Any()).Return(nil).Times(2)
|
|
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[1].acc.GetAddress(), authtypes.FeeCollectorName, gomock.Any()).Return(sdkerrors.ErrInsufficientFunds).Times(2)
|
|
return accs[0], accs[1].acc.GetAddress()
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, stc := range cases {
|
|
tc := stc // to make scopelint happy
|
|
t.Run(name, func(t *testing.T) {
|
|
suite := SetupTestSuite(t, false)
|
|
cdc := codec.NewProtoCodec(suite.encCfg.InterfaceRegistry)
|
|
signingCtx := suite.encCfg.InterfaceRegistry.SigningContext()
|
|
protoTxCfg := tx.NewTxConfig(cdc, signingCtx.AddressCodec(), signingCtx.ValidatorAddressCodec(), tx.DefaultSignModes)
|
|
// this just tests our handler
|
|
dfd := ante.NewDeductFeeDecorator(suite.accountKeeper, suite.bankKeeper, suite.feeGrantKeeper, nil)
|
|
feeAnteHandler := sdk.ChainAnteDecorators(dfd)
|
|
|
|
// this tests the whole stack
|
|
anteHandlerStack := suite.anteHandler
|
|
|
|
signer, feeAcc := stc.malleate(suite)
|
|
|
|
fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee))
|
|
msgs := []sdk.Msg{testdata.NewTestMsg(signer.acc.GetAddress())}
|
|
|
|
acc := suite.accountKeeper.GetAccount(suite.ctx, signer.acc.GetAddress())
|
|
privs, accNums, seqs := []cryptotypes.PrivKey{signer.priv}, []uint64{0}, []uint64{0}
|
|
if acc != nil {
|
|
accNums, seqs = []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()}
|
|
}
|
|
|
|
var defaultGenTxGas uint64 = 10000000
|
|
tx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, defaultGenTxGas, suite.ctx.ChainID(), accNums, seqs, feeAcc, privs...)
|
|
require.NoError(t, err)
|
|
txBytes, err := protoTxCfg.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
bytesCtx := suite.ctx.WithTxBytes(txBytes)
|
|
require.NoError(t, err)
|
|
_, err = feeAnteHandler(bytesCtx, tx, false) // tests only feegrant ante
|
|
if tc.valid {
|
|
require.NoError(t, err)
|
|
} else {
|
|
testutil.AssertError(t, err, tc.err, tc.errMsg)
|
|
}
|
|
|
|
_, err = anteHandlerStack(bytesCtx, tx, false) // tests whole stack
|
|
if tc.valid {
|
|
require.NoError(t, err)
|
|
} else {
|
|
testutil.AssertError(t, err, tc.err, tc.errMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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 := apisigning.SignMode_SIGN_MODE_DIRECT
|
|
|
|
// 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 {
|
|
anyPk, err := codectypes.NewAnyWithValue(p.PubKey())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
signerData := txsigning.SignerData{
|
|
ChainID: chainID,
|
|
AccountNumber: accNums[i],
|
|
Sequence: accSeqs[i],
|
|
PubKey: &anypb.Any{TypeUrl: anyPk.TypeUrl, Value: anyPk.Value},
|
|
}
|
|
signBytes, err := authsign.GetSignBytesAdapter(
|
|
context.Background(), gen.SignModeHandler(), 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
|
|
}
|