diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d56cc8ea9..174731195a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,7 @@ jobs: run: | make build if: "env.GIT_DIFF != ''" + - name: test & coverage report creation run: | go test ./... -mod=readonly -timeout 12m -race -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' diff --git a/tests/cli/helpers.go b/tests/cli/helpers.go index 6c7fa77815..35c5d31b4b 100644 --- a/tests/cli/helpers.go +++ b/tests/cli/helpers.go @@ -158,6 +158,21 @@ func (f *Fixtures) KeyAddress(name string) sdk.AccAddress { return accAddr } +//___________________________________________________________________________________ +// simcli query txs + +// QueryTxs is simcli query txs +func (f *Fixtures) QueryTxs(page, limit int, events ...string) *sdk.SearchTxsResult { + cmd := fmt.Sprintf("%s query txs --page=%d --limit=%d --events='%s' %v", + f.SimcliBinary, page, limit, buildEventsQueryString(events), f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var result sdk.SearchTxsResult + + err := f.Cdc.UnmarshalJSON([]byte(out), &result) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return &result +} + //___________________________________________________________________________________ // simcli config @@ -181,6 +196,10 @@ func UnmarshalStdTx(t require.TestingT, c *codec.Codec, s string) (stdTx auth.St return } +func buildEventsQueryString(events []string) string { + return strings.Join(events, "&") +} + func MarshalStdTx(t require.TestingT, c *codec.Codec, stdTx auth.StdTx) []byte { bz, err := c.MarshalBinaryBare(stdTx) require.NoError(t, err) diff --git a/x/gov/client/cli/cli_test.go b/x/gov/client/cli/cli_test.go new file mode 100644 index 0000000000..0a377ffd66 --- /dev/null +++ b/x/gov/client/cli/cli_test.go @@ -0,0 +1,158 @@ +// +build cli_test + +package cli_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/tests/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + banktestutils "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/gov/client/testutil" + "github.com/stretchr/testify/require" +) + +func TestCLISubmitProposal(t *testing.T) { + t.Parallel() + f := cli.InitFixtures(t) + + // start simd server + proc := f.SDStart() + t.Cleanup(func() { proc.Stop(false) }) + + testutil.QueryGovParamDeposit(f) + testutil.QueryGovParamVoting(f) + testutil.QueryGovParamTallying(f) + + fooAddr := f.KeyAddress(cli.KeyFoo) + + startTokens := sdk.TokensFromConsensusPower(50) + require.Equal(t, startTokens, banktestutils.QueryBalances(f, fooAddr).AmountOf(sdk.DefaultBondDenom)) + + proposalsQuery := testutil.QueryGovProposals(f) + require.Empty(t, proposalsQuery) + + // Test submit generate only for submit proposal + proposalTokens := sdk.TokensFromConsensusPower(5) + success, stdout, stderr := testutil.TxGovSubmitProposal(f, + fooAddr.String(), "Text", "Test", "test", sdk.NewCoin(cli.Denom, proposalTokens), "--generate-only", "-y") + require.True(t, success) + require.Empty(t, stderr) + msg := cli.UnmarshalStdTx(t, f.Cdc, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Test --dry-run + success, _, _ = testutil.TxGovSubmitProposal(f, cli.KeyFoo, "Text", "Test", "test", sdk.NewCoin(cli.Denom, proposalTokens), "--dry-run") + require.True(t, success) + + // Create the proposal + testutil.TxGovSubmitProposal(f, cli.KeyFoo, "Text", "Test", "test", sdk.NewCoin(cli.Denom, proposalTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Ensure transaction events can be queried + searchResult := f.QueryTxs(1, 50, "message.action=submit_proposal", fmt.Sprintf("message.sender=%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) + + // Ensure deposit was deducted + require.Equal(t, startTokens.Sub(proposalTokens), banktestutils.QueryBalances(f, fooAddr).AmountOf(cli.Denom)) + + // Ensure propsal is directly queryable + proposal1 := testutil.QueryGovProposal(f, 1) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusDepositPeriod, proposal1.Status) + + // Ensure query proposals returns properly + proposalsQuery = testutil.QueryGovProposals(f) + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + // Query the deposits on the proposal + deposit := testutil.QueryGovDeposit(f, 1, fooAddr) + require.Equal(t, proposalTokens, deposit.Amount.AmountOf(cli.Denom)) + + // Test deposit generate only + depositTokens := sdk.TokensFromConsensusPower(10) + success, stdout, stderr = testutil.TxGovDeposit(f, 1, fooAddr.String(), sdk.NewCoin(cli.Denom, depositTokens), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = cli.UnmarshalStdTx(t, f.Cdc, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Run the deposit transaction + testutil.TxGovDeposit(f, 1, cli.KeyFoo, sdk.NewCoin(cli.Denom, depositTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // test query deposit + deposits := testutil.QueryGovDeposits(f, 1) + require.Len(t, deposits, 1) + require.Equal(t, proposalTokens.Add(depositTokens), deposits[0].Amount.AmountOf(cli.Denom)) + + // Ensure querying the deposit returns the proper amount + deposit = testutil.QueryGovDeposit(f, 1, fooAddr) + require.Equal(t, proposalTokens.Add(depositTokens), deposit.Amount.AmountOf(cli.Denom)) + + // Ensure events are set on the transaction + searchResult = f.QueryTxs(1, 50, "message.action=deposit", fmt.Sprintf("message.sender=%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) + + // Ensure account has expected amount of funds + require.Equal(t, startTokens.Sub(proposalTokens.Add(depositTokens)), banktestutils.QueryBalances(f, fooAddr).AmountOf(cli.Denom)) + + // Fetch the proposal and ensure it is now in the voting period + proposal1 = testutil.QueryGovProposal(f, 1) + require.Equal(t, uint64(1), proposal1.ProposalID) + require.Equal(t, gov.StatusVotingPeriod, proposal1.Status) + + // Test vote generate only + success, stdout, stderr = testutil.TxGovVote(f, 1, gov.OptionYes, fooAddr.String(), "--generate-only") + require.True(t, success) + require.Empty(t, stderr) + msg = cli.UnmarshalStdTx(t, f.Cdc, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, 0, len(msg.GetSignatures())) + + // Vote on the proposal + testutil.TxGovVote(f, 1, gov.OptionYes, cli.KeyFoo, "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Query the vote + vote := testutil.QueryGovVote(f, 1, fooAddr) + require.Equal(t, uint64(1), vote.ProposalID) + require.Equal(t, gov.OptionYes, vote.Option) + + // Query the votes + votes := testutil.QueryGovVotes(f, 1) + require.Len(t, votes, 1) + require.Equal(t, uint64(1), votes[0].ProposalID) + require.Equal(t, gov.OptionYes, votes[0].Option) + + // Ensure events are applied to voting transaction properly + searchResult = f.QueryTxs(1, 50, "message.action=vote", fmt.Sprintf("message.sender=%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) + + // Ensure no proposals in deposit period + proposalsQuery = testutil.QueryGovProposals(f, "--status=DepositPeriod") + require.Empty(t, proposalsQuery) + + // Ensure the proposal returns as in the voting period + proposalsQuery = testutil.QueryGovProposals(f, "--status=VotingPeriod") + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + // submit a second test proposal + testutil.TxGovSubmitProposal(f, cli.KeyFoo, "Text", "Apples", "test", sdk.NewCoin(cli.Denom, proposalTokens), "-y") + tests.WaitForNextNBlocksTM(1, f.Port) + + // Test limit on proposals query + proposalsQuery = testutil.QueryGovProposals(f, "--limit=2") + require.Len(t, proposalsQuery, 2) + require.Equal(t, uint64(1), proposalsQuery[0].ProposalID) + + f.Cleanup() +} diff --git a/x/gov/client/testutil/helpers.go b/x/gov/client/testutil/helpers.go new file mode 100644 index 0000000000..e28a1a54dd --- /dev/null +++ b/x/gov/client/testutil/helpers.go @@ -0,0 +1,172 @@ +package testutil + +import ( + "fmt" + "strings" + + clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/tests/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/stretchr/testify/require" +) + +//___________________________________________________________________________________ +// simcli query gov + +// QueryGovParamDeposit is simcli query gov param deposit +func QueryGovParamDeposit(f *cli.Fixtures) gov.DepositParams { + cmd := fmt.Sprintf("%s query gov param deposit %s", f.SimcliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var depositParam gov.DepositParams + + err := f.Cdc.UnmarshalJSON([]byte(out), &depositParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return depositParam +} + +// QueryGovParamVoting is simcli query gov param voting +func QueryGovParamVoting(f *cli.Fixtures) gov.VotingParams { + cmd := fmt.Sprintf("%s query gov param voting %s", f.SimcliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var votingParam gov.VotingParams + + err := f.Cdc.UnmarshalJSON([]byte(out), &votingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votingParam +} + +// QueryGovParamTallying is simcli query gov param tallying +func QueryGovParamTallying(f *cli.Fixtures) gov.TallyParams { + cmd := fmt.Sprintf("%s query gov param tallying %s", f.SimcliBinary, f.Flags()) + out, _ := tests.ExecuteT(f.T, cmd, "") + var tallyingParam gov.TallyParams + + err := f.Cdc.UnmarshalJSON([]byte(out), &tallyingParam) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return tallyingParam +} + +// QueryGovProposal is simcli query gov proposal +func QueryGovProposal(f *cli.Fixtures, proposalID int, flags ...string) gov.Proposal { + cmd := fmt.Sprintf("%s query gov proposal %d %v", f.SimcliBinary, proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, cli.AddFlags(cmd, flags), "") + var proposal gov.Proposal + + err := f.Cdc.UnmarshalJSON([]byte(out), &proposal) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return proposal +} + +// QueryGovProposals is simcli query gov proposals +func QueryGovProposals(f *cli.Fixtures, flags ...string) gov.Proposals { + cmd := fmt.Sprintf("%s query gov proposals %v", f.SimcliBinary, f.Flags()) + stdout, stderr := tests.ExecuteT(f.T, cli.AddFlags(cmd, flags), "") + if strings.Contains(stderr, "no matching proposals found") { + return gov.Proposals{} + } + require.Empty(f.T, stderr) + var out gov.Proposals + + err := f.Cdc.UnmarshalJSON([]byte(stdout), &out) + require.NoError(f.T, err) + return out +} + +// QueryGovVote is simcli query gov vote +func QueryGovVote(f *cli.Fixtures, proposalID int, voter sdk.AccAddress, flags ...string) gov.Vote { + cmd := fmt.Sprintf("%s query gov vote %d %s %v", f.SimcliBinary, proposalID, voter, f.Flags()) + out, _ := tests.ExecuteT(f.T, cli.AddFlags(cmd, flags), "") + var vote gov.Vote + + err := f.Cdc.UnmarshalJSON([]byte(out), &vote) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return vote +} + +// QueryGovVotes is simcli query gov votes +func QueryGovVotes(f *cli.Fixtures, proposalID int, flags ...string) []gov.Vote { + cmd := fmt.Sprintf("%s query gov votes %d %v", f.SimcliBinary, proposalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, cli.AddFlags(cmd, flags), "") + var votes []gov.Vote + + err := f.Cdc.UnmarshalJSON([]byte(out), &votes) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return votes +} + +// QueryGovDeposit is simcli query gov deposit +func QueryGovDeposit(f *cli.Fixtures, proposalID int, depositor sdk.AccAddress, flags ...string) gov.Deposit { + cmd := fmt.Sprintf("%s query gov deposit %d %s %v", f.SimcliBinary, proposalID, depositor, f.Flags()) + out, _ := tests.ExecuteT(f.T, cli.AddFlags(cmd, flags), "") + var deposit gov.Deposit + + err := f.Cdc.UnmarshalJSON([]byte(out), &deposit) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposit +} + +// QueryGovDeposits is simcli query gov deposits +func QueryGovDeposits(f *cli.Fixtures, propsalID int, flags ...string) []gov.Deposit { + cmd := fmt.Sprintf("%s query gov deposits %d %v", f.SimcliBinary, propsalID, f.Flags()) + out, _ := tests.ExecuteT(f.T, cli.AddFlags(cmd, flags), "") + var deposits []gov.Deposit + + err := f.Cdc.UnmarshalJSON([]byte(out), &deposits) + require.NoError(f.T, err, "out %v\n, err %v", out, err) + return deposits +} + +//___________________________________________________________________________________ +// simcli tx gov + +// TxGovSubmitProposal is simcli tx gov submit-proposal +func TxGovSubmitProposal(f *cli.Fixtures, from, typ, title, description string, deposit sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx gov submit-proposal %v --keyring-backend=test --from=%s --type=%s", + f.SimcliBinary, f.Flags(), from, typ) + cmd += fmt.Sprintf(" --title=%s --description=%s --deposit=%s", title, description, deposit) + return cli.ExecuteWriteRetStdStreams(f.T, cli.AddFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxGovDeposit is simcli tx gov deposit +func TxGovDeposit(f *cli.Fixtures, proposalID int, from string, amount sdk.Coin, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx gov deposit %d %s --keyring-backend=test --from=%s %v", + f.SimcliBinary, proposalID, amount, from, f.Flags()) + return cli.ExecuteWriteRetStdStreams(f.T, cli.AddFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxGovVote is simcli tx gov vote +func TxGovVote(f *cli.Fixtures, proposalID int, option gov.VoteOption, from string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx gov vote %d %s --keyring-backend=test --from=%s %v", + f.SimcliBinary, proposalID, option, from, f.Flags()) + return cli.ExecuteWriteRetStdStreams(f.T, cli.AddFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxGovSubmitParamChangeProposal executes a CLI parameter change proposal +// submission. +func TxGovSubmitParamChangeProposal(f *cli.Fixtures, + from, proposalPath string, deposit sdk.Coin, flags ...string, +) (bool, string, string) { + + cmd := fmt.Sprintf( + "%s tx gov submit-proposal param-change %s --keyring-backend=test --from=%s %v", + f.SimcliBinary, proposalPath, from, f.Flags(), + ) + + return cli.ExecuteWriteRetStdStreams(f.T, cli.AddFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxGovSubmitCommunityPoolSpendProposal executes a CLI community pool spend proposal +// submission. +func TxGovSubmitCommunityPoolSpendProposal(f *cli.Fixtures, + from, proposalPath string, deposit sdk.Coin, flags ...string, +) (bool, string, string) { + + cmd := fmt.Sprintf( + "%s tx gov submit-proposal community-pool-spend %s --keyring-backend=test --from=%s %v", + f.SimcliBinary, proposalPath, from, f.Flags(), + ) + + return cli.ExecuteWriteRetStdStreams(f.T, cli.AddFlags(cmd, flags), clientkeys.DefaultKeyPass) +}