Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/golang/mock v1.6.0
github.com/google/gofuzz v1.2.0
github.com/gorilla/mux v1.8.1
github.com/hexops/gotextdiff v1.0.3
github.com/manifoldco/promptui v0.9.0
github.com/ory/dockertest/v3 v3.10.0
github.com/rakyll/statik v0.1.7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand Down
2 changes: 1 addition & 1 deletion proto/atomone/gov/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ option go_package = "github.com/atomone-hub/atomone/x/gov/types/v1";
service Query {
// Constitution queries the chain's constitution.
rpc Constitution(QueryConstitutionRequest) returns (QueryConstitutionResponse) {
option (google.api.http).get = "/cosmos/gov/v1/constitution";
option (google.api.http).get = "/atomone/gov/v1/constitution";
}

// Proposal queries proposal details based on ProposalID.
Expand Down
3 changes: 3 additions & 0 deletions proto/atomone/gov/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ message MsgProposeConstitutionAmendment {
// authority is the address that controls the module (defaults to x/gov unless
// overwritten).
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];

// amendment is the amendment to the constitution. It must be in valid GNU patch format.
string amendment = 2;
}

// MsgProposeConstitutionAmendmentResponse defines the response structure for executing a
Expand Down
95 changes: 95 additions & 0 deletions tests/e2e/e2e_gov_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package e2e

import (
"context"
"fmt"
"path/filepath"
"strconv"
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -12,6 +14,7 @@ import (
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

govtypes "github.com/atomone-hub/atomone/x/gov/types"
govtypesv1 "github.com/atomone-hub/atomone/x/gov/types/v1"
govtypesv1beta1 "github.com/atomone-hub/atomone/x/gov/types/v1beta1"
)

Expand Down Expand Up @@ -176,6 +179,34 @@ func (s *IntegrationTestSuite) testGovParamChange() {
})
}

func (s *IntegrationTestSuite) testGovConstitutionAmendment() {
s.Run("constitution amendment", func() {
chainAAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainA.id][0].GetHostPort("1317/tcp"))
senderAddress, _ := s.chainA.validators[0].keyInfo.GetAddress()
sender := senderAddress.String()

newConstitution := "New test constitution"
amendmentMsg := s.generateConstitutionAmendment(s.chainA, newConstitution)

s.writeGovConstitutionAmendmentProposal(s.chainA, amendmentMsg.Amendment)
// Gov tests may be run in arbitrary order, each test must increment proposalCounter to have the correct proposal id to submit and query
proposalCounter++
submitGovFlags := []string{configFile(proposalConstitutionAmendmentFilename)}
depositGovFlags := []string{strconv.Itoa(proposalCounter), depositAmount.String()}
voteGovFlags := []string{strconv.Itoa(proposalCounter), "yes"}
s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "gov/MsgSubmitProposal", submitGovFlags, depositGovFlags, voteGovFlags, "vote")

s.Require().Eventually(
func() bool {
res := s.queryConstitution(chainAAPIEndpoint)
return res.Constitution == newConstitution
},
10*time.Second,
time.Second,
)
})
}

func (s *IntegrationTestSuite) submitLegacyGovProposal(chainAAPIEndpoint, sender string, proposalID int, proposalType string, submitFlags []string, depositFlags []string, voteFlags []string, voteCommand string, withDeposit bool) {
s.T().Logf("Submitting Gov Proposal: %s", proposalType)
// min deposit of 1000uatone is required in e2e tests, otherwise the gov antehandler causes the proposal to be dropped
Expand Down Expand Up @@ -283,3 +314,67 @@ func (s *IntegrationTestSuite) writeStakingParamChangeProposal(c *chain, params
err := writeFile(filepath.Join(c.validators[0].configDir(), "config", proposalParamChangeFilename), []byte(propMsgBody))
s.Require().NoError(err)
}

func (s *IntegrationTestSuite) writeGovConstitutionAmendmentProposal(c *chain, amendment string) {
govModuleAddress := authtypes.NewModuleAddress(govtypes.ModuleName).String()
// escape newlines in amendment
amendment = strings.ReplaceAll(amendment, "\n", "\\n")
template := `
{
"messages":[
{
"@type": "/atomone.gov.v1.MsgProposeConstitutionAmendment",
"authority": "%s",
"amendment": "%s"
}
],
"deposit": "100uatone",
"proposer": "Proposing validator address",
"metadata": "Constitution Amendment",
"title": "Constitution Amendment",
"summary": "summary"
}
`
propMsgBody := fmt.Sprintf(template, govModuleAddress, amendment)
err := writeFile(filepath.Join(c.validators[0].configDir(), "config", proposalConstitutionAmendmentFilename), []byte(propMsgBody))
s.Require().NoError(err)
}

func (s *IntegrationTestSuite) generateConstitutionAmendment(c *chain, newConstitution string) govtypesv1.MsgProposeConstitutionAmendment {
err := writeFile(filepath.Join(c.validators[0].configDir(), "config", newConstitutionFilename), []byte(newConstitution))
s.Require().NoError(err)

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

govCommand := "generate-constitution-amendment"
cmd := []string{
atomonedBinary,
txCommand,
govtypes.ModuleName,
govCommand,
configFile(newConstitutionFilename),
}

s.T().Logf("Executing atomoned tx gov %s on chain %s", govCommand, c.id)
var msg govtypesv1.MsgProposeConstitutionAmendment
s.executeAtomoneTxCommand(ctx, c, cmd, 0, s.parseGenerateConstitutionAmendmentOutput(&msg))
s.T().Logf("Successfully executed %s", govCommand)

s.Require().NoError(err)
return msg
}

func (s *IntegrationTestSuite) parseGenerateConstitutionAmendmentOutput(msg *govtypesv1.MsgProposeConstitutionAmendment) func([]byte, []byte) bool {
return func(stdOut []byte, stdErr []byte) bool {
if len(stdErr) > 0 {
s.T().Logf("Error: %s", string(stdErr))
return false
}

err := cdc.UnmarshalJSON(stdOut, msg)
s.Require().NoError(err)

return true
}
}
10 changes: 6 additions & 4 deletions tests/e2e/e2e_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ const (
numberOfEvidences = 10
slashingShares int64 = 10000

proposalBypassMsgFilename = "proposal_bypass_msg.json"
proposalMaxTotalBypassFilename = "proposal_max_total_bypass.json"
proposalCommunitySpendFilename = "proposal_community_spend.json"
proposalParamChangeFilename = "param_change.json"
proposalBypassMsgFilename = "proposal_bypass_msg.json"
proposalMaxTotalBypassFilename = "proposal_max_total_bypass.json"
proposalCommunitySpendFilename = "proposal_community_spend.json"
proposalParamChangeFilename = "param_change.json"
proposalConstitutionAmendmentFilename = "constitution_amendment.json"
newConstitutionFilename = "new_constitution.md"

// hermesBinary = "hermes"
// hermesConfigWithGasPrices = "/root/.hermes/config.toml"
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (s *IntegrationTestSuite) TestGov() {
s.testGovCancelSoftwareUpgrade()
s.testGovCommunityPoolSpend()
s.testGovParamChange()
s.testGovConstitutionAmendment()
}

func (s *IntegrationTestSuite) TestSlashing() {
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
govv1.DefaultQuorumTimeout, govv1.DefaultMaxVotingPeriodExtension, govv1.DefaultQuorumCheckCount,
),
)
govGenState.Constitution = "This is a test constitution"
govGenStateBz, err := cdc.MarshalJSON(govGenState)
if err != nil {
return fmt.Errorf("failed to marshal gov genesis state: %w", err)
Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

govtypesv1 "github.com/atomone-hub/atomone/x/gov/types/v1"
govtypesv1beta1 "github.com/atomone-hub/atomone/x/gov/types/v1beta1"
)

Expand Down Expand Up @@ -268,3 +269,12 @@ func (s *IntegrationTestSuite) queryStakingParams(endpoint string) stakingtypes.
s.Require().NoError(err)
return res
}

func (s *IntegrationTestSuite) queryConstitution(endpoint string) govtypesv1.QueryConstitutionResponse {
var res govtypesv1.QueryConstitutionResponse
body, err := httpGet(fmt.Sprintf("%s/atomone/gov/v1/constitution", endpoint))
s.Require().NoError(err)
err = cdc.UnmarshalJSON(body, &res)
s.Require().NoError(err)
return res
}
2 changes: 0 additions & 2 deletions x/gov/client/cli/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
Expand Down Expand Up @@ -320,7 +319,6 @@ func NewCmdDraftProposal() *cobra.Command {
},
}

flags.AddTxFlagsToCmd(cmd)
cmd.Flags().Bool(flagSkipMetadata, false, "skip metadata prompt")

return cmd
Expand Down
95 changes: 95 additions & 0 deletions x/gov/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

govutils "github.com/atomone-hub/atomone/x/gov/client/utils"
"github.com/atomone-hub/atomone/x/gov/types"
Expand Down Expand Up @@ -72,6 +73,7 @@ func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command {
NewCmdWeightedVote(),
NewCmdSubmitProposal(),
NewCmdDraftProposal(),
NewCmdGenerateConstitutionAmendment(),

// Deprecated
cmdSubmitLegacyProp,
Expand Down Expand Up @@ -375,3 +377,96 @@ $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.1 --from mykey

return cmd
}

// NewCmdConstitutionAmendmentMsg returns the command to generate the sdk.Msg
// required for a constitution amendment proposal generating the unified diff
// between the current constitution (queried) and the updated constitution
// from the provided markdown file.
func NewCmdGenerateConstitutionAmendment() *cobra.Command {
flagCurrentConstitution := "current-constitution"

cmd := &cobra.Command{
Use: "generate-constitution-amendment [path/to/updated/constitution.md]",
Args: cobra.ExactArgs(1),
Short: "Generate a constitution amendment proposal message",
Long: strings.TrimSpace(
fmt.Sprintf(`Generate a constitution amendment proposal message from the current
constitution and the provided updated constitution.
Queries the current constitution from the node (unless --current-constitution is used)
and generates a valid constitution amendment proposal message containing the unified diff
between the current constitution and the updated constitution provided
in a markdown file.

NOTE: this is just a utility command, it is not able to generate or
submit a valid Tx. Use the 'tx gov submit-proposal' command in
conjunction with the result of this one to submit the proposal.
See also 'tx gov draft-proposal' for a more general proposal drafting tool.

Example:
$ %s tx gov generate-constitution-amendment path/to/updated/constitution.md
`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
// Read the updated constitution from the provided markdown file
updatedConstitution, err := readFromMarkdownFile(args[0])
if err != nil {
return err
}

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

var currentConstitution string
currentConstitutionPath, err := cmd.Flags().GetString(flagCurrentConstitution)
if err != nil {
return err
}

if currentConstitutionPath != "" {
// Read the current constitution from the provided file
currentConstitution, err = readFromMarkdownFile(currentConstitutionPath)
if err != nil {
return err
}
} else {
// Query the current constitution from the node
queryClient := v1.NewQueryClient(clientCtx)
resp, err := queryClient.Constitution(cmd.Context(), &v1.QueryConstitutionRequest{})
if err != nil {
return err
}
currentConstitution = resp.Constitution
}

// Generate the unified diff between the current and updated constitutions
diff, err := govutils.GenerateUnifiedDiff(currentConstitution, updatedConstitution)
if err != nil {
return err
}

// Generate the sdk.Msg for the constitution amendment proposal
msg := v1.NewMsgProposeConstitutionAmendment(authtypes.NewModuleAddress(types.ModuleName), diff)
return clientCtx.PrintProto(msg)
},
}

// This is not a tx command (but a utility for the proposal tx), so we don't need to add tx flags.
// It might actually be confusing, so we just add the query flags.
flags.AddQueryFlagsToCmd(cmd)
// query commands have the FlagOutput default to "text", but we want to override it to "json"
// in this case.
cmd.Flags().Lookup(flags.FlagOutput).DefValue = "json"
err := cmd.Flags().Set(flags.FlagOutput, "json")
if err != nil {
panic(err)
}
// add flag to pass input constitution file instead of querying node
// for the current constitution
cmd.Flags().String(flagCurrentConstitution, "", "Path to the current constitution markdown file (optional, if not provided, the current constitution will be queried from the node)")

return cmd
}
29 changes: 29 additions & 0 deletions x/gov/client/cli/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,32 @@ func (s *CLITestSuite) TestNewCmdWeightedVote() {
})
}
}

func (s *CLITestSuite) TestCmdGenerateConstitutionAmendment() {
newConstitution := `Modified Constitution`
newConstitutionFile := testutil.WriteToNewTempFile(s.T(), newConstitution)
defer newConstitutionFile.Close()

testCases := []struct {
name string
args []string
expCmdOutput string
}{
{
"generate constitution amendment",
[]string{newConstitutionFile.Name()},
"{\"authority\":\"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn\",\"amendment\":\"--- src\\n+++ dst\\n@@ -1 +1 @@\\n-\\n+Modified Constitution\\n\"}",
},
}

for _, tc := range testCases {
tc := tc

s.Run(tc.name, func() {
cmd := cli.NewCmdGenerateConstitutionAmendment()
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args)
s.Require().NoError(err)
s.Require().Contains(out.String(), tc.expCmdOutput)
})
}
}
Loading
Loading