diff --git a/tests/e2e/feegrant/client/testutil/cli_test.go b/tests/e2e/feegrant/cli_test.go similarity index 67% rename from tests/e2e/feegrant/client/testutil/cli_test.go rename to tests/e2e/feegrant/cli_test.go index 7db09c25e2..b0c9255096 100644 --- a/tests/e2e/feegrant/client/testutil/cli_test.go +++ b/tests/e2e/feegrant/cli_test.go @@ -1,14 +1,13 @@ //go:build e2e // +build e2e -package testutil +package feegrant import ( "testing" "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil/network" - clienttestutil "github.com/cosmos/cosmos-sdk/x/feegrant/client/testutil" "github.com/stretchr/testify/suite" ) @@ -16,5 +15,5 @@ import ( func TestIntegrationTestSuite(t *testing.T) { cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) cfg.NumValidators = 3 - suite.Run(t, clienttestutil.NewIntegrationTestSuite(cfg)) + suite.Run(t, NewIntegrationTestSuite(cfg)) } diff --git a/tests/e2e/feegrant/suite.go b/tests/e2e/feegrant/suite.go new file mode 100644 index 0000000000..23bd7edac3 --- /dev/null +++ b/tests/e2e/feegrant/suite.go @@ -0,0 +1,992 @@ +package feegrant + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/suite" + tmcli "github.com/tendermint/tendermint/libs/cli" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/testutil" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/feegrant" + "github.com/cosmos/cosmos-sdk/x/feegrant/client/cli" + govtestutil "github.com/cosmos/cosmos-sdk/x/gov/client/testutil" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" +) + +const ( + oneYear = 365 * 24 * 60 * 60 + tenHours = 10 * 60 * 60 + oneHour = 60 * 60 +) + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + addedGranter sdk.AccAddress + addedGrantee sdk.AccAddress + addedGrant feegrant.Grant +} + +func NewIntegrationTestSuite(cfg network.Config) *IntegrationTestSuite { + return &IntegrationTestSuite{cfg: cfg} +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + if testing.Short() { + s.T().Skip("skipping test in unit-tests mode.") + } + + var err error + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + val := s.network.Validators[0] + granter := val.Address + grantee := s.network.Validators[1].Address + + s.createGrant(granter, grantee) + + grant, err := feegrant.NewGrant(granter, grantee, &feegrant.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))), + }) + s.Require().NoError(err) + + s.addedGrant = grant + s.addedGranter = granter + s.addedGrantee = grantee +} + +// createGrant creates a new basic allowance fee grant from granter to grantee. +func (s *IntegrationTestSuite) createGrant(granter, grantee sdk.Address) { + val := s.network.Validators[0] + + clientCtx := val.ClientCtx + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(100))).String()), + } + + fee := sdk.NewCoin("stake", sdk.NewInt(100)) + + args := append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneYear)), + }, + commonFlags..., + ) + + cmd := cli.NewCmdFeeGrant() + + _, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *IntegrationTestSuite) TestCmdGetFeeGrant() { + val := s.network.Validators[0] + granter := val.Address + grantee := s.addedGrantee + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErrMsg string + expectErr bool + respType *feegrant.Grant + resp *feegrant.Grant + }{ + { + "wrong granter", + []string{ + "wrong_granter", + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "decoding bech32 failed", + true, nil, nil, + }, + { + "wrong grantee", + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "decoding bech32 failed", + true, nil, nil, + }, + { + "non existed grant", + []string{ + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "fee-grant not found", + true, nil, nil, + }, + { + "valid req", + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "", + false, + &feegrant.Grant{}, + &s.addedGrant, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expectErrMsg) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + s.Require().Equal(tc.respType.Grantee, tc.respType.Grantee) + s.Require().Equal(tc.respType.Granter, tc.respType.Granter) + grant, err := tc.respType.GetGrant() + s.Require().NoError(err) + grant1, err1 := tc.resp.GetGrant() + s.Require().NoError(err1) + s.Require().Equal( + grant.(*feegrant.BasicAllowance).SpendLimit, + grant1.(*feegrant.BasicAllowance).SpendLimit, + ) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdGetFeeGrantsByGrantee() { + val := s.network.Validators[0] + grantee := s.addedGrantee + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + resp *feegrant.QueryAllowancesResponse + expectLength int + }{ + { + "wrong grantee", + []string{ + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, nil, 0, + }, + { + "non existent grantee", + []string{ + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &feegrant.QueryAllowancesResponse{}, 0, + }, + { + "valid req", + []string{ + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &feegrant.QueryAllowancesResponse{}, 1, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrantsByGrantee() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.resp), out.String()) + s.Require().Len(tc.resp.Allowances, tc.expectLength) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdGetFeeGrantsByGranter() { + val := s.network.Validators[0] + granter := s.addedGranter + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + resp *feegrant.QueryAllowancesByGranterResponse + expectLength int + }{ + { + "wrong grantee", + []string{ + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, nil, 0, + }, + { + "non existent grantee", + []string{ + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &feegrant.QueryAllowancesByGranterResponse{}, 0, + }, + { + "valid req", + []string{ + granter.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &feegrant.QueryAllowancesByGranterResponse{}, 1, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrantsByGranter() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.resp), out.String()) + s.Require().Len(tc.resp.Allowances, tc.expectLength) + } + }) + } +} + +func (s *IntegrationTestSuite) TestNewCmdFeeGrant() { + val := s.network.Validators[0] + granter := val.Address + alreadyExistedGrantee := s.addedGrantee + clientCtx := val.ClientCtx + + fromAddr, fromName, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, granter.String()) + s.Require().Equal(fromAddr, granter) + s.Require().NoError(err) + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + testCases := []struct { + name string + args []string + expectErr bool + expectedCode uint32 + respType proto.Message + }{ + { + "wrong granter address", + append( + []string{ + "wrong_granter", + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "wrong grantee address", + append( + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "wrong granter key name", + append( + []string{ + "invalid_granter", + "cosmos16dun6ehcc86e03wreqqww89ey569wuj4em572w", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "valid basic fee grant", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant with granter key name", + append( + []string{ + fromName, + "cosmos16dun6ehcc86e03wreqqww89ey569wuj4em572w", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, fromName), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant with amino", + append( + []string{ + granter.String(), + "cosmos1v57fx2l2rt6ehujuu99u2fw05779m5e2ux4z2h", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without spend limit", + append( + []string{ + granter.String(), + "cosmos17h5lzptx3ghvsuhk7wx4c4hnl7rsswxjer97em", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without expiration", + append( + []string{ + granter.String(), + "cosmos16dlc38dcqt0uralyd8hksxyrny6kaeqfjvjwp5", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without spend-limit and expiration", + append( + []string{ + granter.String(), + "cosmos1ku40qup9vwag4wtf8cls9mkszxfthaklxkp3c8", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "try to add existed grant", + append( + []string{ + granter.String(), + alreadyExistedGrantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 18, &sdk.TxResponse{}, + }, + { + "invalid number of args(periodic fee grant)", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "period mentioned and period limit omitted, invalid periodic grant", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, tenHours), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneHour)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "period cannot be greater than the actual expiration(periodic fee grant)", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, tenHours), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneHour)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "valid periodic fee grant", + append( + []string{ + granter.String(), + "cosmos1w55kgcf3ltaqdy4ww49nge3klxmrdavrr6frmp", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without spend-limit", + append( + []string{ + granter.String(), + "cosmos1vevyks8pthkscvgazc97qyfjt40m6g9xe85ry8", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without expiration", + append( + []string{ + granter.String(), + "cosmos14cm33pvnrv2497tyt8sp9yavhmw83nwej3m0e8", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without spend-limit and expiration", + append( + []string{ + granter.String(), + "cosmos12nyk4pcf4arshznkpz882e4l4ts0lt0ap8ce54", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "invalid expiration", + append( + []string{ + granter.String(), + "cosmos1vevyks8pthkscvgazc97qyfjt40m6g9xe85ry8", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, "invalid"), + }, + commonFlags..., + ), + true, 0, nil, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestNewCmdRevokeFeegrant() { + val := s.network.Validators[0] + granter := s.addedGranter + grantee := s.addedGrantee + clientCtx := val.ClientCtx + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + // Create new fee grant specifically to test amino. + aminoGrantee, err := sdk.AccAddressFromBech32("cosmos16ydaqh0fcnh4qt7a3jme4mmztm2qel5axcpw00") + s.Require().NoError(err) + s.createGrant(granter, aminoGrantee) + + testCases := []struct { + name string + args []string + expectErr bool + expectedCode uint32 + respType proto.Message + }{ + { + "invalid grantee", + append( + []string{ + "wrong_granter", + grantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "invalid grantee", + append( + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "Non existed grant", + append( + []string{ + granter.String(), + "cosmos1aeuqja06474dfrj7uqsvukm6rael982kk89mqr", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 38, &sdk.TxResponse{}, + }, + { + "Valid revoke", + append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "Valid revoke with amino", + append( + []string{ + granter.String(), + aminoGrantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdRevokeFeegrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxWithFeeGrant() { + s.T().Skip() // TODO to re-enable in #12274 + + val := s.network.Validators[0] + clientCtx := val.ClientCtx + granter := val.Address + + // creating an account manually (This account won't be exist in state) + k, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + pub, err := k.GetPubKey() + s.Require().NoError(err) + grantee := sdk.AccAddress(pub.Address()) + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + fee := sdk.NewCoin("stake", sdk.NewInt(100)) + + args := append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneYear)), + }, + commonFlags..., + ) + + cmd := cli.NewCmdFeeGrant() + + _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + testcases := []struct { + name string + from string + flags []string + expErrCode uint32 + }{ + { + name: "granted fee allowance for an account which is not in state and creating any tx with it by using --fee-granter shouldn't fail", + from: grantee.String(), + flags: []string{fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String())}, + }, + { + name: "--fee-payer should also sign the tx (direct)", + from: grantee.String(), + flags: []string{fmt.Sprintf("--%s=%s", flags.FlagFeePayer, granter.String())}, + expErrCode: 4, + }, + { + name: "--fee-payer should also sign the tx (amino-json)", + from: grantee.String(), + flags: []string{ + fmt.Sprintf("--%s=%s", flags.FlagFeePayer, granter.String()), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + expErrCode: 4, + }, + { + name: "use --fee-payer and --fee-granter together works", + from: grantee.String(), + flags: []string{ + fmt.Sprintf("--%s=%s", flags.FlagFeePayer, grantee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()), + }, + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + out, err := govtestutil.MsgSubmitLegacyProposal(val.ClientCtx, tc.from, + "Text Proposal", "No desc", govv1beta1.ProposalTypeText, + tc.flags..., + ) + s.Require().NoError(err) + + var resp sdk.TxResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) + s.Require().Equal(tc.expErrCode, resp.Code, resp) + }) + } +} + +func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { + s.T().Skip() // TODO to re-enable in #12274 + + val := s.network.Validators[0] + + granter := val.Address + k, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + pub, err := k.GetPubKey() + s.Require().NoError(err) + grantee := sdk.AccAddress(pub.Address()) + + clientCtx := val.ClientCtx + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(100))).String()), + } + spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) + + allowMsgs := strings.Join([]string{sdk.MsgTypeURL(&govv1beta1.MsgSubmitProposal{}), sdk.MsgTypeURL(&govv1.MsgVoteWeighted{})}, ",") + + testCases := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + "invalid granter address", + append( + []string{ + "not an address", + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "invalid grantee address", + append( + []string{ + granter.String(), + "not an address", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "valid filter fee grant", + append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, &sdk.TxResponse{}, 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } + + args := []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + + // get filtered fee allowance and check info + cmd := cli.GetCmdQueryFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + + resp := &feegrant.Grant{} + + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), resp), out.String()) + s.Require().Equal(resp.Grantee, resp.Grantee) + s.Require().Equal(resp.Granter, resp.Granter) + + grant, err := resp.GetGrant() + s.Require().NoError(err) + + filteredFeeGrant, err := grant.(*feegrant.AllowedMsgAllowance).GetAllowance() + s.Require().NoError(err) + + s.Require().Equal( + filteredFeeGrant.(*feegrant.BasicAllowance).SpendLimit.String(), + spendLimit.String(), + ) + + // exec filtered fee allowance + cases := []struct { + name string + malleate func() (testutil.BufferWriter, error) + respType proto.Message + expectedCode uint32 + }{ + { + "valid proposal tx", + func() (testutil.BufferWriter, error) { + return govtestutil.MsgSubmitLegacyProposal(val.ClientCtx, grantee.String(), + "Text Proposal", "No desc", govv1beta1.ProposalTypeText, + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(100))).String()), + ) + }, + &sdk.TxResponse{}, + 0, + }, + { + "valid weighted_vote tx", + func() (testutil.BufferWriter, error) { + return govtestutil.MsgVote(val.ClientCtx, grantee.String(), "0", "yes", + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(100))).String()), + ) + }, + &sdk.TxResponse{}, + 2, + }, + { + "should fail with unauthorized msgs", + func() (testutil.BufferWriter, error) { + args := append( + []string{ + grantee.String(), + "cosmos14cm33pvnrv2497tyt8sp9yavhmw83nwej3m0e8", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter), + }, + commonFlags..., + ) + cmd := cli.NewCmdFeeGrant() + return clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + }, + &sdk.TxResponse{}, + 7, + }, + } + + for _, tc := range cases { + tc := tc + + s.Run(tc.name, func() { + out, err := tc.malleate() + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + }) + } +} + +func getFormattedExpiration(duration int64) string { + return time.Now().Add(time.Duration(duration) * time.Second).Format(time.RFC3339) +} diff --git a/x/feegrant/client/cli/query_test.go b/x/feegrant/client/cli/query_test.go new file mode 100644 index 0000000000..7b45c4b9d6 --- /dev/null +++ b/x/feegrant/client/cli/query_test.go @@ -0,0 +1,155 @@ +package cli_test + +import ( + "fmt" + + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/x/feegrant" + "github.com/cosmos/cosmos-sdk/x/feegrant/client/cli" + tmcli "github.com/tendermint/tendermint/libs/cli" +) + +func (s *CLITestSuite) TestCmdGetFeeGrant() { + granter := s.addedGranter + grantee := s.addedGrantee + + testCases := []struct { + name string + args []string + expectErrMsg string + expectErr bool + respType *feegrant.QueryAllowanceResponse + resp *feegrant.Grant + }{ + { + "wrong granter", + []string{ + "wrong_granter", + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "decoding bech32 failed", + true, nil, nil, + }, + { + "wrong grantee", + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "decoding bech32 failed", + true, nil, nil, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrant() + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expectErrMsg) + } else { + s.Require().NoError(err) + s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *CLITestSuite) TestCmdGetFeeGrantsByGrantee() { + grantee := s.addedGrantee + clientCtx := s.clientCtx + + testCases := []struct { + name string + args []string + expectErr bool + resp *feegrant.QueryAllowancesResponse + expectLength int + }{ + { + "wrong grantee", + []string{ + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, nil, 0, + }, + { + "valid req", + []string{ + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &feegrant.QueryAllowancesResponse{}, 1, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrantsByGrantee() + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.resp), out.String()) + } + }) + } +} + +func (s *CLITestSuite) TestCmdGetFeeGrantsByGranter() { + granter := s.addedGranter + clientCtx := s.clientCtx + + testCases := []struct { + name string + args []string + expectErr bool + resp *feegrant.QueryAllowancesByGranterResponse + expectLength int + }{ + { + "wrong grantee", + []string{ + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, nil, 0, + }, + { + "valid req", + []string{ + granter.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &feegrant.QueryAllowancesByGranterResponse{}, 1, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrantsByGranter() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.resp), out.String()) + } + }) + } +} diff --git a/x/feegrant/client/cli/tx_test.go b/x/feegrant/client/cli/tx_test.go new file mode 100644 index 0000000000..de7e6a78f3 --- /dev/null +++ b/x/feegrant/client/cli/tx_test.go @@ -0,0 +1,839 @@ +package cli_test + +import ( + "bytes" + "context" + "fmt" + "io" + "strings" + "testing" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmlibs "github.com/tendermint/tendermint/libs/bytes" + rpcclient "github.com/tendermint/tendermint/rpc/client" + rpcclientmock "github.com/tendermint/tendermint/rpc/client/mock" + coretypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/testutil" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/cosmos/cosmos-sdk/x/feegrant" + "github.com/cosmos/cosmos-sdk/x/feegrant/client/cli" + "github.com/cosmos/cosmos-sdk/x/feegrant/module" + govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" +) + +const ( + oneYear = 365 * 24 * 60 * 60 + tenHours = 10 * 60 * 60 + oneHour = 60 * 60 +) + +type CLITestSuite struct { + suite.Suite + + addedGranter sdk.AccAddress + addedGrantee sdk.AccAddress + addedGrant feegrant.Grant + + kr keyring.Keyring + baseCtx client.Context + encCfg testutilmod.TestEncodingConfig + clientCtx client.Context + + accounts []sdk.AccAddress +} + +var _ client.TendermintRPC = (*mockTendermintRPC)(nil) + +type mockTendermintRPC struct { + rpcclientmock.Client + + responseQuery abci.ResponseQuery +} + +func newMockTendermintRPC(respQuery abci.ResponseQuery) mockTendermintRPC { + return mockTendermintRPC{responseQuery: respQuery} +} + +func (m mockTendermintRPC) ABCIQueryWithOptions( + _ context.Context, + _ string, _ tmlibs.HexBytes, + _ rpcclient.ABCIQueryOptions, +) (*coretypes.ResultABCIQuery, error) { + return &coretypes.ResultABCIQuery{Response: m.responseQuery}, nil +} + +func (m mockTendermintRPC) BroadcastTxSync(context.Context, tmtypes.Tx) (*coretypes.ResultBroadcastTx, error) { + return &coretypes.ResultBroadcastTx{Code: 0}, nil +} + +func TestCLITestSuite(t *testing.T) { + suite.Run(t, new(CLITestSuite)) +} + +func (s *CLITestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + s.encCfg = testutilmod.MakeTestEncodingConfig(module.AppModuleBasic{}) + s.kr = keyring.NewInMemory(s.encCfg.Codec) + s.baseCtx = client.Context{}. + WithKeyring(s.kr). + WithTxConfig(s.encCfg.TxConfig). + WithCodec(s.encCfg.Codec). + WithClient(mockTendermintRPC{Client: rpcclientmock.Client{}}). + WithAccountRetriever(client.MockAccountRetriever{}). + WithOutput(io.Discard). + WithChainID("test-chain") + + var outBuf bytes.Buffer + ctxGen := func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{}) + c := newMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + + return s.baseCtx.WithClient(c) + } + s.clientCtx = ctxGen().WithOutput(&outBuf) + + if testing.Short() { + s.T().Skip("skipping test in unit-tests mode.") + } + + accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 2) + + granter := accounts[0].Address + grantee := accounts[1].Address + + s.createGrant(granter, grantee) + + grant, err := feegrant.NewGrant(granter, grantee, &feegrant.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))), + }) + s.Require().NoError(err) + + s.addedGrant = grant + s.addedGranter = granter + s.addedGrantee = grantee + for _, v := range accounts { + s.accounts = append(s.accounts, v.Address) + } + s.accounts[1] = accounts[1].Address +} + +// createGrant creates a new basic allowance fee grant from granter to grantee. +func (s *CLITestSuite) createGrant(granter, grantee sdk.Address) { + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))).String()), + } + + fee := sdk.NewCoin("stake", sdk.NewInt(100)) + + args := append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneYear)), + }, + commonFlags..., + ) + + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) + s.Require().NoError(err) + + var resp sdk.TxResponse + s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) + s.Require().Equal(resp.Code, uint32(0)) +} + +func (s *CLITestSuite) TestNewCmdFeeGrant() { + granter := s.accounts[0] + alreadyExistedGrantee := s.addedGrantee + clientCtx := s.clientCtx + + fromAddr, fromName, _, err := client.GetFromFields(s.baseCtx, s.kr, granter.String()) + s.Require().Equal(fromAddr, granter) + s.Require().NoError(err) + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10))).String()), + } + + testCases := []struct { + name string + args []string + expectErr bool + expectedCode uint32 + respType proto.Message + }{ + { + "wrong granter address", + append( + []string{ + "wrong_granter", + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "wrong grantee address", + append( + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "wrong granter key name", + append( + []string{ + "invalid_granter", + "cosmos16dun6ehcc86e03wreqqww89ey569wuj4em572w", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "valid basic fee grant", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant with granter key name", + append( + []string{ + fromName, + "cosmos16dun6ehcc86e03wreqqww89ey569wuj4em572w", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, fromName), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant with amino", + append( + []string{ + granter.String(), + "cosmos1v57fx2l2rt6ehujuu99u2fw05779m5e2ux4z2h", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without spend limit", + append( + []string{ + granter.String(), + "cosmos17h5lzptx3ghvsuhk7wx4c4hnl7rsswxjer97em", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without expiration", + append( + []string{ + granter.String(), + "cosmos16dlc38dcqt0uralyd8hksxyrny6kaeqfjvjwp5", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without spend-limit and expiration", + append( + []string{ + granter.String(), + "cosmos1ku40qup9vwag4wtf8cls9mkszxfthaklxkp3c8", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "try to add existed grant", + append( + []string{ + granter.String(), + alreadyExistedGrantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 18, &sdk.TxResponse{}, + }, + { + "invalid number of args(periodic fee grant)", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "period mentioned and period limit omitted, invalid periodic grant", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, tenHours), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneHour)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "period cannot be greater than the actual expiration(periodic fee grant)", + append( + []string{ + granter.String(), + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, tenHours), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneHour)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "valid periodic fee grant", + append( + []string{ + granter.String(), + "cosmos1w55kgcf3ltaqdy4ww49nge3klxmrdavrr6frmp", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without spend-limit", + append( + []string{ + granter.String(), + "cosmos1vevyks8pthkscvgazc97qyfjt40m6g9xe85ry8", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without expiration", + append( + []string{ + granter.String(), + "cosmos14cm33pvnrv2497tyt8sp9yavhmw83nwej3m0e8", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without spend-limit and expiration", + append( + []string{ + granter.String(), + "cosmos12nyk4pcf4arshznkpz882e4l4ts0lt0ap8ce54", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "invalid expiration", + append( + []string{ + granter.String(), + "cosmos1vevyks8pthkscvgazc97qyfjt40m6g9xe85ry8", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, "invalid"), + }, + commonFlags..., + ), + true, 0, nil, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *CLITestSuite) TestNewCmdRevokeFeegrant() { + granter := s.addedGranter + grantee := s.addedGrantee + clientCtx := s.clientCtx + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10))).String()), + } + + // Create new fee grant specifically to test amino. + aminoGrantee, err := sdk.AccAddressFromBech32("cosmos16ydaqh0fcnh4qt7a3jme4mmztm2qel5axcpw00") + s.Require().NoError(err) + s.createGrant(granter, aminoGrantee) + + testCases := []struct { + name string + args []string + expectErr bool + expectedCode uint32 + respType proto.Message + }{ + { + "invalid grantee", + append( + []string{ + "wrong_granter", + grantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "invalid grantee", + append( + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "Valid revoke", + append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "Valid revoke with amino", + append( + []string{ + granter.String(), + aminoGrantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdRevokeFeegrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *CLITestSuite) TestTxWithFeeGrant() { + // s.T().Skip() // TODO to re-enable in #12274 + + clientCtx := s.clientCtx + granter := s.addedGranter + + // creating an account manually (This account won't be exist in state) + k, _, err := s.baseCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + pub, err := k.GetPubKey() + s.Require().NoError(err) + grantee := sdk.AccAddress(pub.Address()) + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10))).String()), + } + + fee := sdk.NewCoin("stake", sdk.NewInt(100)) + + args := append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneYear)), + }, + commonFlags..., + ) + + cmd := cli.NewCmdFeeGrant() + + var res sdk.TxResponse + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res), out.String()) + + testcases := []struct { + name string + from string + flags []string + expErrCode uint32 + }{ + { + name: "granted fee allowance for an account which is not in state and creating any tx with it by using --fee-granter shouldn't fail", + from: grantee.String(), + flags: []string{fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String())}, + }, + { + name: "--fee-payer should also sign the tx (direct)", + from: grantee.String(), + flags: []string{fmt.Sprintf("--%s=%s", flags.FlagFeePayer, granter.String())}, + expErrCode: 4, + }, + { + name: "--fee-payer should also sign the tx (amino-json)", + from: grantee.String(), + flags: []string{ + fmt.Sprintf("--%s=%s", flags.FlagFeePayer, granter.String()), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + expErrCode: 4, + }, + { + name: "use --fee-payer and --fee-granter together works", + from: grantee.String(), + flags: []string{ + fmt.Sprintf("--%s=%s", flags.FlagFeePayer, grantee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()), + }, + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + err := s.msgSubmitLegacyProposal(s.baseCtx, tc.from, + "Text Proposal", "No desc", govv1beta1.ProposalTypeText, + tc.flags..., + ) + s.Require().NoError(err) + + var resp sdk.TxResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) + }) + } +} + +func (s *CLITestSuite) msgSubmitLegacyProposal(clientCtx client.Context, from, title, description, proposalType string, extraArgs ...string) error { + + var commonArgs = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + } + + args := append([]string{ + fmt.Sprintf("--%s=%s", govcli.FlagTitle, title), + fmt.Sprintf("--%s=%s", govcli.FlagDescription, description), + fmt.Sprintf("--%s=%s", govcli.FlagProposalType, proposalType), + fmt.Sprintf("--%s=%s", flags.FlagFrom, from), + }, commonArgs...) + + args = append(args, extraArgs...) + + cmd := govcli.NewCmdSubmitLegacyProposal() + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + var resp sdk.TxResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &resp), out.String()) + + return err +} + +func (s *CLITestSuite) TestFilteredFeeAllowance() { + granter := s.addedGranter + k, _, err := s.baseCtx.Keyring.NewMnemonic("grantee1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + pub, err := k.GetPubKey() + s.Require().NoError(err) + grantee := sdk.AccAddress(pub.Address()) + + clientCtx := s.clientCtx + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))).String()), + } + spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) + + allowMsgs := strings.Join([]string{sdk.MsgTypeURL(&govv1beta1.MsgSubmitProposal{}), sdk.MsgTypeURL(&govv1.MsgVoteWeighted{})}, ",") + + testCases := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + "invalid granter address", + append( + []string{ + "not an address", + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "invalid grantee address", + append( + []string{ + granter.String(), + "not an address", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "valid filter fee grant", + append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, &sdk.TxResponse{}, 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } + + // exec filtered fee allowance + cases := []struct { + name string + malleate func() error + respType proto.Message + expectedCode uint32 + }{ + { + "valid proposal tx", + func() error { + return s.msgSubmitLegacyProposal(s.baseCtx, grantee.String(), + "Text Proposal", "No desc", govv1beta1.ProposalTypeText, + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))).String()), + ) + }, + &sdk.TxResponse{}, + 0, + }, + { + "valid weighted_vote tx", + func() error { + return s.msgVote(s.baseCtx, grantee.String(), "0", "yes", + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))).String()), + ) + }, + &sdk.TxResponse{}, + 2, + }, + { + "should fail with unauthorized msgs", + func() error { + args := append( + []string{ + grantee.String(), + "cosmos14cm33pvnrv2497tyt8sp9yavhmw83nwej3m0e8", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter), + }, + commonFlags..., + ) + + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &sdk.TxResponse{}), out.String()) + + return err + }, + &sdk.TxResponse{}, + 7, + }, + } + + for _, tc := range cases { + tc := tc + + s.Run(tc.name, func() { + err := tc.malleate() + s.Require().NoError(err) + }) + } +} + +// msgVote votes for a proposal +func (s *CLITestSuite) msgVote(clientCtx client.Context, from, id, vote string, extraArgs ...string) error { + var commonArgs = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + } + args := append([]string{ + id, + vote, + fmt.Sprintf("--%s=%s", flags.FlagFrom, from), + }, commonArgs...) + + args = append(args, extraArgs...) + cmd := govcli.NewCmdWeightedVote() + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &sdk.TxResponse{}), out.String()) + + return err +} + +func getFormattedExpiration(duration int64) string { + return time.Now().Add(time.Duration(duration) * time.Second).Format(time.RFC3339) +}