diff --git a/CHANGELOG.md b/CHANGELOG.md index 4980866f126d..7ee37c2e8b1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,10 +44,13 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features - * [\#9383](https://github.com/cosmos/cosmos-sdk/pull/9383) New CLI command `query ibc-transfer escrow-address ` to get the escrow address for a channel; can be used to then query balance of escrowed tokens * (baseapp, types) [#\9390](https://github.com/cosmos/cosmos-sdk/pull/9390) Add current block header hash to `Context` +### Bug Fixes + +* (x/gov) [\#8813](https://github.com/cosmos/cosmos-sdk/pull/8813) fix `GET /cosmos/gov/v1beta1/proposals/{proposal_id}/deposits` to include initial deposit + ## [v0.42.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.42.5) - 2021-05-18 diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index 8c67cd96e5e1..1b7f7995f42e 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -364,8 +364,9 @@ $ %s query gov deposit 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk } // check to see if the proposal is in the store - _, err = queryClient.Proposal( - context.Background(), + ctx := cmd.Context() + proposalRes, err := queryClient.Proposal( + ctx, &types.QueryProposalRequest{ProposalId: proposalID}, ) if err != nil { @@ -377,25 +378,27 @@ $ %s query gov deposit 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk return err } - res, err := queryClient.Deposit( - context.Background(), - &types.QueryDepositRequest{ProposalId: proposalID, Depositor: args[1]}, - ) - if err != nil { - return err - } - - deposit := res.GetDeposit() - if deposit.Empty() { + var deposit types.Deposit + propStatus := proposalRes.Proposal.Status + if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) { params := types.NewQueryDepositParams(proposalID, depositorAddr) resByTxQuery, err := gcutils.QueryDepositByTxQuery(clientCtx, params) if err != nil { return err } clientCtx.JSONMarshaler.MustUnmarshalJSON(resByTxQuery, &deposit) + return clientCtx.PrintProto(&deposit) + } + + res, err := queryClient.Deposit( + ctx, + &types.QueryDepositRequest{ProposalId: proposalID, Depositor: args[1]}, + ) + if err != nil { + return err } - return clientCtx.PrintProto(&deposit) + return clientCtx.PrintProto(&res.Deposit) }, } diff --git a/x/gov/client/testutil/cli_test.go b/x/gov/client/testutil/cli_test.go new file mode 100644 index 000000000000..e28ba13dcd2d --- /dev/null +++ b/x/gov/client/testutil/cli_test.go @@ -0,0 +1,27 @@ +// +build norace + +package testutil + +import ( + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +func TestIntegrationTestSuite(t *testing.T) { + cfg := network.DefaultConfig() + cfg.NumValidators = 1 + genesisState := types.DefaultGenesisState() + genesisState.DepositParams = types.NewDepositParams(sdk.NewCoins(sdk.NewCoin(cfg.BondDenom, types.DefaultMinDepositTokens)), time.Duration(15)*time.Second) + genesisState.VotingParams = types.NewVotingParams(time.Duration(5) * time.Second) + bz, err := cfg.Codec.MarshalJSON(genesisState) + require.NoError(t, err) + cfg.GenesisState["gov"] = bz + suite.Run(t, NewDepositTestSuite(cfg)) +} diff --git a/x/gov/client/testutil/deposits.go b/x/gov/client/testutil/deposits.go new file mode 100644 index 000000000000..57c3c9363330 --- /dev/null +++ b/x/gov/client/testutil/deposits.go @@ -0,0 +1,193 @@ +package testutil + +import ( + "fmt" + "time" + + 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/gov/client/cli" + "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/stretchr/testify/suite" + tmcli "github.com/tendermint/tendermint/libs/cli" +) + +type DepositTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + fees string +} + +func NewDepositTestSuite(cfg network.Config) *DepositTestSuite { + return &DepositTestSuite{cfg: cfg} +} + +func (s *DepositTestSuite) SetupSuite() { + s.T().Log("setting up test suite") + + s.network = network.New(s.T(), s.cfg) + + _, err := s.network.WaitForHeight(1) + s.Require().NoError(err) + s.fees = sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(20))).String() + +} + +func (s *DepositTestSuite) TearDownSuite() { + s.T().Log("tearing down test suite") + s.network.Cleanup() +} + +func (s *DepositTestSuite) TestQueryDepositsInitialDeposit() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + initialDeposit := sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens.Sub(sdk.NewInt(20))).String() + + // create a proposal with deposit + _, err := MsgSubmitProposal(val.ClientCtx, val.Address.String(), + "Text Proposal 1", "Where is the title!?", types.ProposalTypeText, + fmt.Sprintf("--%s=%s", cli.FlagDeposit, initialDeposit)) + s.Require().NoError(err) + + // deposit more amount + _, err = MsgDeposit(clientCtx, val.Address.String(), "1", sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(50)).String()) + s.Require().NoError(err) + + // waiting for voting period to end + time.Sleep(20 * time.Second) + + // query deposit & verify initial deposit + deposit := s.queryDeposit(val, "1", false) + s.Require().Equal(deposit.Amount.String(), initialDeposit) + + // query deposits + deposits := s.queryDeposits(val, "1", false) + s.Require().Equal(len(deposits), 2) + // verify initial deposit + s.Require().Equal(deposits[0].Amount.String(), initialDeposit) +} + +func (s *DepositTestSuite) TestQueryDepositsWithoutInitialDeposit() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + // create a proposal without deposit + _, err := MsgSubmitProposal(val.ClientCtx, val.Address.String(), + "Text Proposal 2", "Where is the title!?", types.ProposalTypeText) + s.Require().NoError(err) + + // deposit amount + _, err = MsgDeposit(clientCtx, val.Address.String(), "2", sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens.Add(sdk.NewInt(50))).String()) + s.Require().NoError(err) + + // waiting for voting period to end + time.Sleep(20 * time.Second) + + // query deposit + deposit := s.queryDeposit(val, "2", false) + s.Require().Equal(deposit.Amount.String(), sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens.Add(sdk.NewInt(50))).String()) + + // query deposits + deposits := s.queryDeposits(val, "2", false) + s.Require().Equal(len(deposits), 1) + // verify initial deposit + s.Require().Equal(deposits[0].Amount.String(), sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens.Add(sdk.NewInt(50))).String()) +} + +func (s *DepositTestSuite) TestQueryProposalNotEnoughDeposits() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + initialDeposit := sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens.Sub(sdk.NewInt(2000))).String() + + // create a proposal with deposit + _, err := MsgSubmitProposal(val.ClientCtx, val.Address.String(), + "Text Proposal 3", "Where is the title!?", types.ProposalTypeText, + fmt.Sprintf("--%s=%s", cli.FlagDeposit, initialDeposit)) + s.Require().NoError(err) + + // query proposal + args := []string{"3", fmt.Sprintf("--%s=json", tmcli.OutputFlag)} + cmd := cli.GetCmdQueryProposal() + _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + + // waiting for deposit period to end + time.Sleep(20 * time.Second) + + // query proposal + _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().Error(err) + s.Require().Contains(err.Error(), "proposal 3 doesn't exist") +} + +func (s *DepositTestSuite) TestRejectedProposalDeposits() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + initialDeposit := sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens) + + // create a proposal with deposit + _, err := MsgSubmitProposal(clientCtx, val.Address.String(), + "Text Proposal 4", "Where is the title!?", types.ProposalTypeText, + fmt.Sprintf("--%s=%s", cli.FlagDeposit, initialDeposit)) + s.Require().NoError(err) + + // query deposits + var deposits types.QueryDepositsResponse + args := []string{"4", fmt.Sprintf("--%s=json", tmcli.OutputFlag)} + cmd := cli.GetCmdQueryDeposits() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) + s.Require().NoError(err) + s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(out.Bytes(), &deposits)) + s.Require().Equal(len(deposits.Deposits), 1) + // verify initial deposit + s.Require().Equal(deposits.Deposits[0].Amount.String(), sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens).String()) + + // vote + _, err = MsgVote(clientCtx, val.Address.String(), "4", "no") + s.Require().NoError(err) + + time.Sleep(20 * time.Second) + + args = []string{"4", fmt.Sprintf("--%s=json", tmcli.OutputFlag)} + cmd = cli.GetCmdQueryProposal() + _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + + // query deposits + depositsRes := s.queryDeposits(val, "4", false) + s.Require().Equal(len(depositsRes), 1) + // verify initial deposit + s.Require().Equal(depositsRes[0].Amount.String(), initialDeposit.String()) + +} + +func (s *DepositTestSuite) queryDeposits(val *network.Validator, proposalID string, exceptErr bool) types.Deposits { + args := []string{proposalID, fmt.Sprintf("--%s=json", tmcli.OutputFlag)} + var depositsRes types.Deposits + cmd := cli.GetCmdQueryDeposits() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) + if exceptErr { + s.Require().Error(err) + return nil + } + s.Require().NoError(err) + s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(out.Bytes(), &depositsRes)) + return depositsRes +} + +func (s *DepositTestSuite) queryDeposit(val *network.Validator, proposalID string, exceptErr bool) *types.Deposit { + args := []string{proposalID, val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)} + var depositRes types.Deposit + cmd := cli.GetCmdQueryDeposit() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) + if exceptErr { + s.Require().Error(err) + return nil + } + s.Require().NoError(err) + s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(out.Bytes(), &depositRes)) + return &depositRes +} diff --git a/x/gov/client/testutil/helpers.go b/x/gov/client/testutil/helpers.go index 535cb8d09ca6..36fdabbbcd2f 100644 --- a/x/gov/client/testutil/helpers.go +++ b/x/gov/client/testutil/helpers.go @@ -43,3 +43,15 @@ func MsgVote(clientCtx client.Context, from, id, vote string, extraArgs ...strin return clitestutil.ExecTestCLICmd(clientCtx, govcli.NewCmdVote(), args) } + +func MsgDeposit(clientCtx client.Context, from, id, deposit string, extraArgs ...string) (testutil.BufferWriter, error) { + args := append([]string{ + id, + deposit, + fmt.Sprintf("--%s=%s", flags.FlagFrom, from), + }, commonArgs...) + + args = append(args, extraArgs...) + + return clitestutil.ExecTestCLICmd(clientCtx, govcli.NewCmdDeposit(), args) +} diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index e543f526b3f0..c3c590da30fe 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authclient "github.com/cosmos/cosmos-sdk/x/auth/client" "github.com/cosmos/cosmos-sdk/x/gov/types" ) @@ -37,6 +38,18 @@ func (p Proposer) String() string { // NOTE: SearchTxs is used to facilitate the txs query which does not currently // support configurable pagination. func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposalParams) ([]byte, error) { + var deposits []types.Deposit + + // initial deposit was submitted with proposal, so must be queried separately + initialDeposit, err := queryInitialDepositByTxQuery(clientCtx, params.ProposalID) + if err != nil { + return nil, err + } + + if !initialDeposit.Amount.IsZero() { + deposits = append(deposits, initialDeposit) + } + events := []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), @@ -49,8 +62,6 @@ func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposal return nil, err } - var deposits []types.Deposit - for _, info := range searchResult.Txs { for _, msg := range info.GetTx().GetMsgs() { if msg.Type() == types.TypeMsgDeposit { @@ -167,6 +178,21 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) // QueryDepositByTxQuery will query for a single deposit via a direct txs tags // query. func QueryDepositByTxQuery(clientCtx client.Context, params types.QueryDepositParams) ([]byte, error) { + // initial deposit was submitted with proposal, so must be queried separately + initialDeposit, err := queryInitialDepositByTxQuery(clientCtx, params.ProposalID) + if err != nil { + return nil, err + } + + if !initialDeposit.Amount.IsZero() { + bz, err := clientCtx.JSONMarshaler.MarshalJSON(&initialDeposit) + if err != nil { + return nil, err + } + + return bz, nil + } + events := []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), @@ -248,3 +274,33 @@ func QueryProposalByID(proposalID uint64, clientCtx client.Context, queryRoute s return res, err } + +// queryInitialDepositByTxQuery will query for a initial deposit of a governance proposal by +// ID. +func queryInitialDepositByTxQuery(clientCtx client.Context, proposalID uint64) (types.Deposit, error) { + // Query legacy Msgs event action + events := []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal), + fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))), + } + searchResult, err := authclient.QueryTxsByEvents(clientCtx, events, defaultPage, defaultLimit, "") + + if err != nil { + return types.Deposit{}, err + } + + for _, info := range searchResult.Txs { + for _, msg := range info.GetTx().GetMsgs() { + // there should only be a single proposal under the given conditions + if subMsg, ok := msg.(*types.MsgSubmitProposal); ok { + return types.Deposit{ + ProposalId: proposalID, + Depositor: subMsg.Proposer, + Amount: subMsg.InitialDeposit, + }, nil + } + } + } + + return types.Deposit{}, sdkerrors.Wrapf(sdkerrors.ErrKeyNotFound, "failed to find the initial deposit for proposalID %d", proposalID) +}