From 58a7d7a1f1281259fee6434e9b38bbb3c6048128 Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Mon, 23 Sep 2024 19:19:51 +0200 Subject: [PATCH 01/11] amendments edit constitution Use unified diff notation to define amendments so that how the constitution is modified is encoded in a more compact way which is also more readable --- go.mod | 1 + go.sum | 2 + proto/atomone/gov/v1/tx.proto | 3 + x/gov/keeper/common_test.go | 7 +- x/gov/keeper/constitution.go | 17 ++ x/gov/keeper/constitution_test.go | 39 +++ x/gov/keeper/msg_server.go | 10 +- x/gov/keeper/msg_server_test.go | 52 ++++ x/gov/simulation/proposals.go | 7 +- x/gov/types/errors.go | 25 +- x/gov/types/unified_diff.go | 303 +++++++++++++++++++++++ x/gov/types/unified_diff_test.go | 386 ++++++++++++++++++++++++++++++ x/gov/types/v1/msgs.go | 16 ++ x/gov/types/v1/msgs_test.go | 25 ++ x/gov/types/v1/tx.pb.go | 183 +++++++++----- 15 files changed, 990 insertions(+), 86 deletions(-) create mode 100644 x/gov/keeper/constitution_test.go create mode 100644 x/gov/types/unified_diff.go create mode 100644 x/gov/types/unified_diff_test.go diff --git a/go.mod b/go.mod index 1ae1c66ac..55ec0236f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 1b51101ae..16c0cf34a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/proto/atomone/gov/v1/tx.proto b/proto/atomone/gov/v1/tx.proto index a5991e813..f05db7f93 100644 --- a/proto/atomone/gov/v1/tx.proto +++ b/proto/atomone/gov/v1/tx.proto @@ -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 diff --git a/x/gov/keeper/common_test.go b/x/gov/keeper/common_test.go index b91cbf608..ca86503ce 100644 --- a/x/gov/keeper/common_test.go +++ b/x/gov/keeper/common_test.go @@ -50,13 +50,12 @@ func getTestProposal() []sdk.Msg { // getTestConstitutionAmendmentProposal creates and returns a test constitution amendment proposal message. func getTestConstitutionAmendmentProposal() []sdk.Msg { - proposalMsg := v1.MsgProposeConstitutionAmendment{ - Authority: authtypes.NewModuleAddress(types.ModuleName).String(), - } + amendment := "@@ -1 +1 @@\n-\n+Test" + proposalMsg := v1.NewMsgProposeConstitutionAmendment(authtypes.NewModuleAddress(types.ModuleName), amendment) return []sdk.Msg{ banktypes.NewMsgSend(govAcct, addr, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000)))), - &proposalMsg, + proposalMsg, } } diff --git a/x/gov/keeper/constitution.go b/x/gov/keeper/constitution.go index 143c54752..15618438f 100644 --- a/x/gov/keeper/constitution.go +++ b/x/gov/keeper/constitution.go @@ -17,3 +17,20 @@ func (keeper Keeper) SetConstitution(ctx sdk.Context, constitution string) { store := ctx.KVStore(keeper.storeKey) store.Set(types.KeyConstitution, []byte(constitution)) } + +// ApplyConstitutionAmendment applies the amendment as a patch against the current constitution +// and returns the updated constitution. If the amendment cannot be applied cleanly, an error is returned. +func (k Keeper) ApplyConstitutionAmendment(ctx sdk.Context, amendment string) (updatedConstitution string, err error) { + if amendment == "" { + return "", types.ErrInvalidConstitutionAmendment.Wrap("amendment cannot be empty") + } + + currentConstitution := k.GetConstitution(ctx) + updatedConstitution, err = types.ApplyUnifiedDiff(currentConstitution, amendment) + + if err != nil { + return "", types.ErrInvalidConstitutionAmendment.Wrapf("failed to apply amendment: %v", err) + } + + return updatedConstitution, nil +} diff --git a/x/gov/keeper/constitution_test.go b/x/gov/keeper/constitution_test.go new file mode 100644 index 000000000..a031c5f29 --- /dev/null +++ b/x/gov/keeper/constitution_test.go @@ -0,0 +1,39 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestApplyConstitutionAmendment(t *testing.T) { + govKeeper, _, _, ctx := setupGovKeeper(t) + + tests := []struct { + name string + initialConstitution string + amendment string + expectedResult string + expectError bool + }{ + { + name: "failed patch application", + initialConstitution: "Hello World", + amendment: "Hi World", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + govKeeper.SetConstitution(ctx, tt.initialConstitution) + updatedConstitution, err := govKeeper.ApplyConstitutionAmendment(ctx, tt.amendment) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedResult, updatedConstitution) + } + }) + } +} diff --git a/x/gov/keeper/msg_server.go b/x/gov/keeper/msg_server.go index 86d442146..6ef0bc5dd 100644 --- a/x/gov/keeper/msg_server.go +++ b/x/gov/keeper/msg_server.go @@ -203,7 +203,15 @@ func (k msgServer) ProposeConstitutionAmendment(goCtx context.Context, msg *v1.M if k.authority != msg.Authority { return nil, errors.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.authority, msg.Authority) } - // only a no-op for now + if msg.Amendment == "" { + return nil, govtypes.ErrInvalidProposalMsg.Wrap("amendment cannot be empty") + } + ctx := sdk.UnwrapSDKContext(goCtx) + constitution, err := k.ApplyConstitutionAmendment(ctx, msg.Amendment) + if err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrap(err.Error()) + } + k.SetConstitution(ctx, constitution) return &v1.MsgProposeConstitutionAmendmentResponse{}, nil } diff --git a/x/gov/keeper/msg_server_test.go b/x/gov/keeper/msg_server_test.go index 41d610654..5d5b8041b 100644 --- a/x/gov/keeper/msg_server_test.go +++ b/x/gov/keeper/msg_server_test.go @@ -10,6 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/atomone-hub/atomone/x/gov/types" v1 "github.com/atomone-hub/atomone/x/gov/types/v1" "github.com/atomone-hub/atomone/x/gov/types/v1beta1" ) @@ -1300,3 +1301,54 @@ func (suite *KeeperTestSuite) TestSubmitProposal_InitialDeposit() { }) } } + +func (suite *KeeperTestSuite) TestProposeConstitutionAmendment() { + ctx := suite.ctx + suite.govKeeper.SetConstitution(ctx, "Hello World") + + cases := map[string]struct { + msg *v1.MsgProposeConstitutionAmendment + expErr bool + expErrMsg string + expResult string + }{ + "successful amendment": { + msg: v1.NewMsgProposeConstitutionAmendment( + suite.govKeeper.GetGovernanceAccount(ctx).GetAddress(), + "@@ -1 +1 @@\n-Hello World\n+Hi World", + ), + expErr: false, + expResult: "Hi World", + }, + "failed amendment": { + msg: v1.NewMsgProposeConstitutionAmendment( + suite.govKeeper.GetGovernanceAccount(ctx).GetAddress(), + "invalid patch", + ), + expErr: true, + }, + "invalid authority": { + msg: v1.NewMsgProposeConstitutionAmendment( + sdk.AccAddress("invalid"), + "@@ -1 +1 @@\n-Hello World\n+Hi World", + ), + expErr: true, + expErrMsg: govtypes.ErrInvalidSigner.Error(), + }, + } + + for name, tc := range cases { + suite.Run(name, func() { + _, err := suite.msgSrvr.ProposeConstitutionAmendment(sdk.WrapSDKContext(ctx), tc.msg) + if tc.expErr { + suite.Require().Error(err) + if tc.expErrMsg != "" { + suite.Require().Contains(err.Error(), tc.expErrMsg) + } + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expResult, suite.govKeeper.GetConstitution(ctx)) + } + }) + } +} diff --git a/x/gov/simulation/proposals.go b/x/gov/simulation/proposals.go index e31b580b3..2d23293dc 100644 --- a/x/gov/simulation/proposals.go +++ b/x/gov/simulation/proposals.go @@ -73,10 +73,9 @@ func SimulateLegacyTextProposalContent(r *rand.Rand, _ sdk.Context, _ []simtypes } // SimulateConstitutionAmendmentProposal returns a random constitution amendment proposal. -func SimulateConstitutionAmendmentProposal(_ *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { - return &v1.MsgProposeConstitutionAmendment{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - } +func SimulateConstitutionAmendmentProposal(r *rand.Rand, ctx sdk.Context, _ []simtypes.Account) sdk.Msg { + amendment := simtypes.RandStringOfLength(r, 140) + return v1.NewMsgProposeConstitutionAmendment(authtypes.NewModuleAddress(govtypes.ModuleName), amendment) } // SimulateLawProposal returns a random law proposal. diff --git a/x/gov/types/errors.go b/x/gov/types/errors.go index 19b410820..c00889dc6 100644 --- a/x/gov/types/errors.go +++ b/x/gov/types/errors.go @@ -10,16 +10,17 @@ var ( ErrInactiveProposal = sdkerrors.Register(ModuleName, 30, "inactive proposal") //nolint:staticcheck ErrAlreadyActiveProposal = sdkerrors.Register(ModuleName, 40, "proposal already active") //nolint:staticcheck // Errors 5 & 6 are legacy errors related to v1beta1.Proposal. - ErrInvalidProposalContent = sdkerrors.Register(ModuleName, 50, "invalid proposal content") //nolint:staticcheck - ErrInvalidProposalType = sdkerrors.Register(ModuleName, 60, "invalid proposal type") //nolint:staticcheck - ErrInvalidVote = sdkerrors.Register(ModuleName, 70, "invalid vote option") //nolint:staticcheck - ErrInvalidGenesis = sdkerrors.Register(ModuleName, 80, "invalid genesis state") //nolint:staticcheck - ErrNoProposalHandlerExists = sdkerrors.Register(ModuleName, 90, "no handler exists for proposal type") //nolint:staticcheck - ErrUnroutableProposalMsg = sdkerrors.Register(ModuleName, 100, "proposal message not recognized by router") //nolint:staticcheck - ErrNoProposalMsgs = sdkerrors.Register(ModuleName, 110, "no messages proposed") //nolint:staticcheck - ErrInvalidProposalMsg = sdkerrors.Register(ModuleName, 120, "invalid proposal message") //nolint:staticcheck - ErrInvalidSigner = sdkerrors.Register(ModuleName, 130, "expected gov account as only signer for proposal message") //nolint:staticcheck - ErrInvalidSignalMsg = sdkerrors.Register(ModuleName, 140, "signal message is invalid") //nolint:staticcheck - ErrMetadataTooLong = sdkerrors.Register(ModuleName, 150, "metadata too long") //nolint:staticcheck - ErrMinDepositTooSmall = sdkerrors.Register(ModuleName, 160, "minimum deposit is too small") //nolint:staticcheck + ErrInvalidProposalContent = sdkerrors.Register(ModuleName, 50, "invalid proposal content") //nolint:staticcheck + ErrInvalidProposalType = sdkerrors.Register(ModuleName, 60, "invalid proposal type") //nolint:staticcheck + ErrInvalidVote = sdkerrors.Register(ModuleName, 70, "invalid vote option") //nolint:staticcheck + ErrInvalidGenesis = sdkerrors.Register(ModuleName, 80, "invalid genesis state") //nolint:staticcheck + ErrNoProposalHandlerExists = sdkerrors.Register(ModuleName, 90, "no handler exists for proposal type") //nolint:staticcheck + ErrUnroutableProposalMsg = sdkerrors.Register(ModuleName, 100, "proposal message not recognized by router") //nolint:staticcheck + ErrNoProposalMsgs = sdkerrors.Register(ModuleName, 110, "no messages proposed") //nolint:staticcheck + ErrInvalidProposalMsg = sdkerrors.Register(ModuleName, 120, "invalid proposal message") //nolint:staticcheck + ErrInvalidSigner = sdkerrors.Register(ModuleName, 130, "expected gov account as only signer for proposal message") //nolint:staticcheck + ErrInvalidSignalMsg = sdkerrors.Register(ModuleName, 140, "signal message is invalid") //nolint:staticcheck + ErrMetadataTooLong = sdkerrors.Register(ModuleName, 150, "metadata too long") //nolint:staticcheck + ErrMinDepositTooSmall = sdkerrors.Register(ModuleName, 160, "minimum deposit is too small") //nolint:staticcheck + ErrInvalidConstitutionAmendment = sdkerrors.Register(ModuleName, 170, "invalid constitution amendment") //nolint:staticcheck ) diff --git a/x/gov/types/unified_diff.go b/x/gov/types/unified_diff.go new file mode 100644 index 000000000..fa7a9aa3e --- /dev/null +++ b/x/gov/types/unified_diff.go @@ -0,0 +1,303 @@ +package types + +import ( + "fmt" + "strconv" + "strings" + + "github.com/hexops/gotextdiff" + "github.com/hexops/gotextdiff/myers" + "github.com/hexops/gotextdiff/span" +) + +// Hunk represents a single change hunk in a unified diff. +type Hunk struct { + SrcLine int + SrcSpan int + DstLine int + DstSpan int + Lines []string +} + +// ParseUnifiedDiff parses the unified diff string into hunks with validation. +func ParseUnifiedDiff(diffStr string) ([]Hunk, error) { + var ( + hunks []Hunk + hunk *Hunk + ) + + diffLines := strings.Split(diffStr, "\n") + // remove any trailing empty lines + for len(diffLines) > 0 && diffLines[len(diffLines)-1] == "" { + diffLines = diffLines[:len(diffLines)-1] + } + + for i := 0; i < len(diffLines); i++ { + line := diffLines[i] + + if strings.HasPrefix(line, "@@") { + // Start of a new hunk + hunkHeader := line + h, err := parseHunkHeader(hunkHeader) + if err != nil { + return nil, fmt.Errorf("invalid hunk header at line %d: %v", i+1, err) + } + if hunk != nil { + // Validate the previous hunk before starting a new one + if err := validateHunk(hunk); err != nil { + return nil, fmt.Errorf("invalid hunk ending at line %d: %v", i, err) + } + hunks = append(hunks, *hunk) + } + hunk = h + continue + } + + if hunk != nil { + if len(line) > 0 { + prefix := line[0] + if prefix != ' ' && prefix != '-' && prefix != '+' { + return nil, fmt.Errorf("invalid line prefix '%c' at line %d", prefix, i+1) + } + hunk.Lines = append(hunk.Lines, line) + } else { + // Empty line (could be a valid diff line with no content) + hunk.Lines = append(hunk.Lines, line) + } + } else if strings.TrimSpace(line) != "" && !strings.HasPrefix(line, "---") && !strings.HasPrefix(line, "+++") { + // Non-empty line outside of a hunk and not a file header + return nil, fmt.Errorf("unexpected content outside of hunks at line %d", i+1) + } + } + + // Validate and append the last hunk + if hunk != nil { + if err := validateHunk(hunk); err != nil { + return nil, fmt.Errorf("invalid hunk at end of diff: %v", err) + } + hunks = append(hunks, *hunk) + } + + if len(hunks) == 0 { + return nil, fmt.Errorf("no valid hunks found in the diff") + } + + return hunks, nil +} + +// parseHunkHeader parses the hunk header line and returns a Hunk. +func parseHunkHeader(header string) (*Hunk, error) { + // Remove the leading and trailing '@' symbols and any surrounding spaces + header = strings.TrimPrefix(header, "@@") + header = strings.TrimSuffix(header, "@@") + header = strings.TrimSpace(header) + + // Split the header into source and destination parts + parts := strings.Fields(header) + if len(parts) < 2 { + return nil, fmt.Errorf("invalid hunk header format") + } + + srcPart := parts[0] + dstPart := parts[1] + + h := &Hunk{} + + // Parse source part + srcLine, srcSpan, err := parseHunkRange(srcPart) + if err != nil { + return nil, fmt.Errorf("invalid source range: %v", err) + } + h.SrcLine = srcLine + h.SrcSpan = srcSpan + + // Parse destination part + dstLine, dstSpan, err := parseHunkRange(dstPart) + if err != nil { + return nil, fmt.Errorf("invalid destination range: %v", err) + } + h.DstLine = dstLine + h.DstSpan = dstSpan + + return h, nil +} + +// parseHunkRange parses a range string like "-srcLine,srcSpan" or "+dstLine" +func parseHunkRange(rangeStr string) (line int, span int, err error) { + if len(rangeStr) == 0 { + return 0, 0, fmt.Errorf("empty range string") + } + + // The range string starts with '-' or '+' + prefix := rangeStr[0] + if prefix != '-' && prefix != '+' { + return 0, 0, fmt.Errorf("invalid range prefix '%c'", prefix) + } + + rangeStr = rangeStr[1:] // Remove the prefix + + // Split the range into line and span if ',' is present + if strings.Contains(rangeStr, ",") { + parts := strings.Split(rangeStr, ",") + if len(parts) != 2 { + return 0, 0, fmt.Errorf("invalid range format") + } + line, err = strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, fmt.Errorf("invalid line number: %v", err) + } + span, err = strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, fmt.Errorf("invalid span number: %v", err) + } + } else { + // No span provided, default to span = 1 + line, err = strconv.Atoi(rangeStr) + if err != nil { + return 0, 0, fmt.Errorf("invalid line number: %v", err) + } + span = 1 + } + + if span < 0 { + return 0, 0, fmt.Errorf("negative span") + } + + line-- // Adjust line number to 0-based index + + return line, span, nil +} + +// validateHunk validates the hunk's content against its header. +func validateHunk(hunk *Hunk) error { + srcCount := 0 + dstCount := 0 + + for _, line := range hunk.Lines { + if len(line) == 0 { + // Empty line, does not contribute to counts + continue + } + switch line[0] { + case ' ': + srcCount++ + dstCount++ + case '-': + srcCount++ + case '+': + dstCount++ + default: + return fmt.Errorf("invalid line prefix '%c'", line[0]) + } + } + + if srcCount != hunk.SrcSpan { + return fmt.Errorf("source line count (%d) does not match SrcSpan (%d)", srcCount, hunk.SrcSpan) + } + if dstCount != hunk.DstSpan { + return fmt.Errorf("destination line count (%d) does not match DstSpan (%d)", dstCount, hunk.DstSpan) + } + + return nil +} + +// applyHunks applies the parsed hunks to the source lines. +func applyHunks(srcStr string, hunks []Hunk) ([]string, error) { + srcLines := strings.Split(srcStr, "\n") + result := make([]string, 0) + srcIndex := 0 + + for _, hunk := range hunks { + // Add unchanged lines before the hunk + for srcIndex < hunk.SrcLine { + result = append(result, srcLines[srcIndex]) + srcIndex++ + } + + // Apply hunk lines + for _, line := range hunk.Lines { + if len(line) == 0 { + continue + } + prefix := line[0] + content := line[1:] + + switch prefix { + case ' ': + // Context line, should match source + if srcIndex >= len(srcLines) || srcLines[srcIndex] != content { + return nil, fmt.Errorf("context line mismatch at line %d", srcIndex+1) + } + result = append(result, content) + srcIndex++ + case '-': + // Deletion, skip source line + if srcIndex >= len(srcLines) || srcLines[srcIndex] != content { + return nil, fmt.Errorf("deletion line mismatch at line %d", srcIndex+1) + } + srcIndex++ + case '+': + // Insertion, add to result + result = append(result, content) + default: + return nil, fmt.Errorf("invalid diff line: %s", line) + } + } + } + + // Add any remaining lines + for srcIndex < len(srcLines) { + result = append(result, srcLines[srcIndex]) + srcIndex++ + } + + return result, nil +} + +// ApplyUnifiedDiff applies a unified diff patch to the src string and returns the result. +// Does not make use of any external libraries to ensure deterministic behavior. +func ApplyUnifiedDiff(src, diffStr string) (string, error) { + // Parse the unified diff into hunks + hunks, err := ParseUnifiedDiff(diffStr) + if err != nil { + return "", err + } + + // Apply the hunks to the source lines + resultLines, err := applyHunks(src, hunks) + if err != nil { + return "", err + } + + return strings.Join(resultLines, "\n"), nil +} + +// GenerateUnifiedDiff generates a unified diff from src and dst strings using gotextdiff. +// This is the only function that uses the gotextdiff library as its primary use is for +// clients. +func GenerateUnifiedDiff(src, dst string) (string, error) { + if src == "" || dst == "" { + return "", fmt.Errorf("source and destination strings cannot be empty") + } + + // Create spans for the source and destination texts + srcURI := span.URIFromPath("src") + // add an EOL to src and dst if they don't have one + if src[len(src)-1] != '\n' { + src += "\n" + } + if dst[len(dst)-1] != '\n' { + dst += "\n" + } + + // Compute the edits using the Myers diff algorithm + eds := myers.ComputeEdits(span.URI(srcURI), src, dst) + + // Generate the unified diff string + diff := gotextdiff.ToUnified("src", "dst", src, eds) + + // Convert the diff to a string + diffStr := fmt.Sprintf("%v", diff) + + return diffStr, nil +} diff --git a/x/gov/types/unified_diff_test.go b/x/gov/types/unified_diff_test.go new file mode 100644 index 000000000..25120f4dd --- /dev/null +++ b/x/gov/types/unified_diff_test.go @@ -0,0 +1,386 @@ +package types + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGenerateUnifiedDiff(t *testing.T) { + tests := []struct { + name string + src string + dst string + expected string + }{ + { + name: "No changes", + src: "Line one\nLine two\nLine three", + dst: "Line one\nLine two\nLine three", + expected: ``, + }, + { + name: "Line added", + src: "Line one\nLine two", + dst: "Line one\nLine two\nLine three", + expected: `@@ -1,2 +1,3 @@ + Line one + Line two ++Line three +`, + }, + { + name: "Line deleted", + src: "Line one\nLine two\nLine three", + dst: "Line one\nLine three", + expected: `@@ -1,3 +1,2 @@ + Line one +-Line two + Line three +`, + }, + { + name: "Line modified", + src: "Line one\nLine two\nLine three", + dst: "Line one\nLine two modified\nLine three", + expected: `@@ -1,3 +1,3 @@ + Line one +-Line two ++Line two modified + Line three +`, + }, + { + name: "Multiple changes", + src: "Line one\nLine two\nLine three", + dst: "Line zero\nLine one\nLine three\nLine four", + expected: `@@ -1,3 +1,4 @@ ++Line zero + Line one +-Line two + Line three ++Line four +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + diff, err := GenerateUnifiedDiff(tt.src, tt.dst) + require.NoError(t, err) + + diffContent := strings.TrimPrefix(diff, "--- src\n+++ dst\n") + expectedContent := strings.TrimPrefix(tt.expected, "--- src\n+++ dst\n") + + require.Equal(t, expectedContent, diffContent) + }) + } +} + +func TestApplyUnifiedDiff(t *testing.T) { + tests := []struct { + name string + src string + diffStr string + expected string + wantErr bool + }{ + { + name: "Apply addition", + src: "Line one\nLine two", + diffStr: `@@ -1,2 +1,3 @@ + Line one + Line two ++Line three +`, + expected: "Line one\nLine two\nLine three", + wantErr: false, + }, + { + name: "Apply deletion", + src: "Line one\nLine two\nLine three", + diffStr: `@@ -1,3 +1,2 @@ + Line one +-Line two + Line three +`, + expected: "Line one\nLine three", + wantErr: false, + }, + { + name: "Apply modification", + src: "Line one\nLine two\nLine three", + diffStr: `@@ -2 +2 @@ +-Line two ++Line two modified +`, + expected: "Line one\nLine two modified\nLine three", + wantErr: false, + }, + { + name: "Apply multiple changes", + src: "Line one\nLine two\nLine three", + diffStr: `@@ -1,3 +1,4 @@ ++Line zero + Line one +-Line two + Line three ++Line four +`, + expected: "Line zero\nLine one\nLine three\nLine four", + wantErr: false, + }, + { + name: "Malformed diff", + src: "Line one\nLine two", + diffStr: `@@ -1,2 +1,3 @@ + Line one ++Line three +`, + expected: "", + wantErr: true, + }, + { + name: "Context line mismatch", + src: "Line one\nLine two", + diffStr: `@@ -1,2 +1,2 @@ + Line zero + Line two +`, + expected: "", + wantErr: true, + }, + { + name: "Empty diff", + src: "Line one\nLine two", + diffStr: ``, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := ApplyUnifiedDiff(tt.src, tt.diffStr) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, result) + } + }) + } +} + +func TestUnifiedDiffIntegration(t *testing.T) { + src := "Line one\nLine two\nLine three" + dst := "Line zero\nLine one\nLine three\nLine four" + + diffStr, err := GenerateUnifiedDiff(src, dst) + require.NoError(t, err) + + result, err := ApplyUnifiedDiff(src, diffStr) + require.NoError(t, err) + require.Equal(t, dst, result) +} + +func TestParseUnifiedDiff(t *testing.T) { + diffStr := `@@ -1,3 +1,4 @@ ++Line zero + Line one +-Line two + Line three ++Line four +` + + expectedHunks := []Hunk{ + { + SrcLine: 0, + SrcSpan: 3, + DstLine: 0, + DstSpan: 4, + Lines: []string{ + "+Line zero", + " Line one", + "-Line two", + " Line three", + "+Line four", + }, + }, + } + + t.Run("Hunk with spans", func(t *testing.T) { + hunks, err := ParseUnifiedDiff(diffStr) + require.NoError(t, err) + require.Equal(t, expectedHunks, hunks) + }) + + diffWithoutSpans := `@@ -2 +2 @@ +-Line two ++Line two modified +` + + expectedHunksWithoutSpans := []Hunk{ + { + SrcLine: 1, + SrcSpan: 1, + DstLine: 1, + DstSpan: 1, + Lines: []string{ + "-Line two", + "+Line two modified", + }, + }, + } + + t.Run("Hunk header without spans", func(t *testing.T) { + hunks, err := ParseUnifiedDiff(diffWithoutSpans) + require.NoError(t, err) + require.Equal(t, expectedHunksWithoutSpans, hunks) + }) + + invalidDiffs := []struct { + name string + diffStr string + wantErr bool + errorMsg string + }{ + { + name: "Invalid hunk header format", + diffStr: `@@ invalid header @@ + Line one +`, + wantErr: true, + errorMsg: "invalid hunk header", + }, + { + name: "Negative span in hunk header", + diffStr: `@@ -1,-2 +1,2 @@ + Line one +`, + wantErr: true, + errorMsg: "negative span", + }, + { + name: "Invalid line prefix", + diffStr: `@@ -1,1 +1,1 @@ +?Line one +`, + wantErr: true, + errorMsg: "invalid line prefix", + }, + { + name: "Source line count mismatch", + diffStr: `@@ -1,2 +1,2 @@ + Line one ++Line two +`, + wantErr: true, + errorMsg: "source line count", + }, + { + name: "Destination line count mismatch", + diffStr: `@@ -1,2 +1,2 @@ +-Line one + Line two +`, + wantErr: true, + errorMsg: "destination line count", + }, + { + name: "No hunks", + diffStr: ``, + wantErr: true, + errorMsg: "no valid hunks", + }, + { + name: "Unexpected content outside hunks", + diffStr: `Unexpected line +@@ -1,1 +1,1 @@ + Line one +`, + wantErr: true, + errorMsg: "unexpected content outside of hunks", + }, + } + + for _, tt := range invalidDiffs { + t.Run(tt.name, func(t *testing.T) { + _, err := ParseUnifiedDiff(tt.diffStr) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMsg) + } else { + require.NoError(t, err) + } + }) + } +} + +// TestParseHunkHeader tests the parseHunkHeader function. +func TestParseHunkHeader(t *testing.T) { + tests := []struct { + name string + header string + expected *Hunk + wantErr bool + }{ + { + name: "Header with spans", + header: "@@ -1,3 +1,4 @@", + expected: &Hunk{ + SrcLine: 0, + SrcSpan: 3, + DstLine: 0, + DstSpan: 4, + }, + wantErr: false, + }, + { + name: "Header without spans", + header: "@@ -2 +2 @@", + expected: &Hunk{ + SrcLine: 1, + SrcSpan: 1, + DstLine: 1, + DstSpan: 1, + }, + wantErr: false, + }, + { + name: "Header with zero spans", + header: "@@ -0,0 +1,2 @@", + expected: &Hunk{ + SrcLine: -1, + SrcSpan: 0, + DstLine: 0, + DstSpan: 2, + }, + wantErr: false, + }, + { + name: "Invalid header format", + header: "@@ invalid header @@", + expected: nil, + wantErr: true, + }, + { + name: "Negative span", + header: "@@ -1,-2 +1,2 @@", + expected: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hunk, err := parseHunkHeader(tt.header) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, hunk) + } + }) + } +} diff --git a/x/gov/types/v1/msgs.go b/x/gov/types/v1/msgs.go index 3e4dd5c1a..6b0103719 100644 --- a/x/gov/types/v1/msgs.go +++ b/x/gov/types/v1/msgs.go @@ -323,6 +323,13 @@ func (msg MsgUpdateParams) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{authority} } +func NewMsgProposeConstitutionAmendment(authority sdk.AccAddress, amendment string) *MsgProposeConstitutionAmendment { + return &MsgProposeConstitutionAmendment{ + Authority: authority.String(), + Amendment: amendment, + } +} + // Route implements the sdk.Msg interface. func (msg MsgProposeConstitutionAmendment) Route() string { return types.RouterKey } @@ -335,6 +342,15 @@ func (msg MsgProposeConstitutionAmendment) ValidateBasic() error { return sdkerrors.ErrInvalidAddress.Wrapf("invalid authority address: %s", err) } + if msg.Amendment == "" { + return types.ErrInvalidProposalContent.Wrap("amendment cannot be empty") + } + + _, err := types.ParseUnifiedDiff(msg.Amendment) + if err != nil { + return types.ErrInvalidProposalContent.Wrap(err.Error()) + } + return nil } diff --git a/x/gov/types/v1/msgs_test.go b/x/gov/types/v1/msgs_test.go index d8cffa7eb..f3b2370ce 100644 --- a/x/gov/types/v1/msgs_test.go +++ b/x/gov/types/v1/msgs_test.go @@ -210,3 +210,28 @@ func TestMsgSubmitProposal_GetSignBytes(t *testing.T) { }) } } + +func TestMsgProposeConstitutionAmendment_ValidateBasic(t *testing.T) { + tests := []struct { + name string + proposer string + amendment string + expErr bool + }{ + {"invalid proposer", "", "amendment", true}, + {"empty amendment", addrs[0].String(), "", true}, + {"valid", addrs[0].String(), "@@ -1 +1 @@\n-\n+Test", false}, + } + + for _, tc := range tests { + msg := v1.MsgProposeConstitutionAmendment{ + Authority: tc.proposer, + Amendment: tc.amendment, + } + if tc.expErr { + require.Error(t, msg.ValidateBasic(), "test: %s", tc.name) + } else { + require.NoError(t, msg.ValidateBasic(), "test: %s", tc.name) + } + } +} diff --git a/x/gov/types/v1/tx.pb.go b/x/gov/types/v1/tx.pb.go index b0aaf6744..7dd0c1abc 100644 --- a/x/gov/types/v1/tx.pb.go +++ b/x/gov/types/v1/tx.pb.go @@ -780,6 +780,8 @@ type MsgProposeConstitutionAmendment struct { // authority is the address that controls the module (defaults to x/gov unless // overwritten). Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // amendment is the amendment to the constitution. It must be in valid GNU patch format. + Amendment string `protobuf:"bytes,2,opt,name=amendment,proto3" json:"amendment,omitempty"` } func (m *MsgProposeConstitutionAmendment) Reset() { *m = MsgProposeConstitutionAmendment{} } @@ -822,6 +824,13 @@ func (m *MsgProposeConstitutionAmendment) GetAuthority() string { return "" } +func (m *MsgProposeConstitutionAmendment) GetAmendment() string { + if m != nil { + return m.Amendment + } + return "" +} + // MsgProposeConstitutionAmendmentResponse defines the response structure for executing a // MsgProposeConstitutionAmendment message. type MsgProposeConstitutionAmendmentResponse struct { @@ -884,71 +893,72 @@ func init() { func init() { proto.RegisterFile("atomone/gov/v1/tx.proto", fileDescriptor_f6c84786701fca8d) } var fileDescriptor_f6c84786701fca8d = []byte{ - // 1020 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x6f, 0x1b, 0x45, - 0x14, 0xce, 0x26, 0xb1, 0xdd, 0xbc, 0x40, 0xaa, 0xac, 0x5c, 0xbc, 0x59, 0x82, 0x9d, 0xae, 0x8a, - 0x9a, 0x44, 0x64, 0x17, 0xbb, 0x40, 0x85, 0x95, 0x03, 0x71, 0x40, 0xa8, 0x52, 0xad, 0x56, 0xae, - 0xf8, 0x21, 0x0e, 0x44, 0x63, 0x7b, 0x98, 0xac, 0x94, 0xdd, 0x59, 0x79, 0xc6, 0x26, 0xe6, 0x84, - 0x38, 0x21, 0x4e, 0x88, 0xbf, 0x82, 0x63, 0x0e, 0xbd, 0xf4, 0x1f, 0x40, 0x15, 0xa7, 0x8a, 0x13, - 0xa7, 0x0a, 0x25, 0x87, 0x48, 0xdc, 0x39, 0x22, 0xa1, 0xd9, 0x99, 0x5d, 0x7b, 0xbd, 0x6b, 0x3b, - 0xe4, 0xd0, 0x8b, 0xb5, 0xf3, 0xde, 0xfb, 0xde, 0x7c, 0xef, 0x9b, 0xf7, 0x66, 0x0c, 0x25, 0xc4, - 0xa9, 0x47, 0x7d, 0xec, 0x10, 0x3a, 0x70, 0x06, 0x55, 0x87, 0x9f, 0xda, 0x41, 0x8f, 0x72, 0xaa, - 0xaf, 0x29, 0x87, 0x4d, 0xe8, 0xc0, 0x1e, 0x54, 0xcd, 0x72, 0x87, 0x32, 0x8f, 0x32, 0xa7, 0x8d, - 0x18, 0x76, 0x06, 0xd5, 0x36, 0xe6, 0xa8, 0xea, 0x74, 0xa8, 0xeb, 0xcb, 0x78, 0xd3, 0x98, 0x48, - 0x24, 0x60, 0xd2, 0x53, 0x24, 0x94, 0xd0, 0xf0, 0xd3, 0x11, 0x5f, 0xca, 0xba, 0x21, 0xf3, 0x1d, - 0x49, 0x87, 0x5c, 0x44, 0x2e, 0x42, 0x29, 0x39, 0xc1, 0x4e, 0xb8, 0x6a, 0xf7, 0xbf, 0x71, 0x90, - 0x3f, 0x54, 0xae, 0x92, 0x62, 0xe1, 0x31, 0x22, 0x36, 0xf1, 0x18, 0x51, 0x8e, 0x75, 0xe4, 0xb9, - 0x3e, 0x75, 0xc2, 0x5f, 0x69, 0xb2, 0x7e, 0x5b, 0x84, 0xf5, 0x26, 0x23, 0x4f, 0xfa, 0x6d, 0xcf, - 0xe5, 0x8f, 0x7b, 0x34, 0xa0, 0x0c, 0x9d, 0xe8, 0xef, 0xc2, 0x0d, 0x0f, 0x33, 0x86, 0x08, 0x66, - 0x86, 0xb6, 0xb5, 0xb4, 0xbd, 0x5a, 0x2b, 0xda, 0x72, 0x3f, 0x3b, 0xda, 0xcf, 0x3e, 0xf0, 0x87, - 0xad, 0x38, 0x4a, 0x6f, 0xc2, 0x4d, 0xd7, 0x77, 0xb9, 0x8b, 0x4e, 0x8e, 0xba, 0x38, 0xa0, 0xcc, - 0xe5, 0xc6, 0x62, 0x08, 0xdc, 0xb0, 0x15, 0x6d, 0xa1, 0x89, 0xad, 0x34, 0xb1, 0x0f, 0xa9, 0xeb, - 0x37, 0x56, 0x9e, 0xbf, 0xac, 0x2c, 0xfc, 0x7a, 0x79, 0xb6, 0xab, 0xb5, 0xd6, 0x14, 0xf8, 0x63, - 0x89, 0xd5, 0xdf, 0x83, 0x1b, 0x41, 0x48, 0x06, 0xf7, 0x8c, 0xa5, 0x2d, 0x6d, 0x7b, 0xa5, 0x61, - 0xfc, 0xf1, 0x74, 0xaf, 0xa8, 0x52, 0x1d, 0x74, 0xbb, 0x3d, 0xcc, 0xd8, 0x13, 0xde, 0x73, 0x7d, - 0xd2, 0x8a, 0x23, 0x75, 0x53, 0xd0, 0xe6, 0xa8, 0x8b, 0x38, 0x32, 0x96, 0x05, 0xaa, 0x15, 0xaf, - 0xf5, 0x22, 0xe4, 0xb8, 0xcb, 0x4f, 0xb0, 0x91, 0x0b, 0x1d, 0x72, 0xa1, 0x1b, 0x50, 0x60, 0x7d, - 0xcf, 0x43, 0xbd, 0xa1, 0x91, 0x0f, 0xed, 0xd1, 0xb2, 0x6e, 0xff, 0x70, 0x79, 0xb6, 0x1b, 0xa7, - 0xfe, 0xe9, 0xf2, 0x6c, 0x77, 0x33, 0x3a, 0xbc, 0x41, 0xd5, 0x49, 0x49, 0x66, 0xed, 0xc3, 0x46, - 0xca, 0xd8, 0xc2, 0x2c, 0xa0, 0x3e, 0xc3, 0x7a, 0x05, 0x56, 0x03, 0x65, 0x3b, 0x72, 0xbb, 0x86, - 0xb6, 0xa5, 0x6d, 0x2f, 0xb7, 0x20, 0x32, 0x3d, 0xe8, 0x5a, 0xcf, 0x34, 0x28, 0x36, 0x19, 0xf9, - 0xe4, 0x14, 0x77, 0x1e, 0x62, 0x82, 0x3a, 0xc3, 0x43, 0xea, 0x73, 0xec, 0x73, 0xfd, 0x11, 0x14, - 0x3a, 0xf2, 0x33, 0x44, 0x4d, 0x39, 0x88, 0x46, 0xe5, 0xf7, 0xa7, 0x7b, 0x6f, 0x26, 0x9b, 0x31, - 0x12, 0x3a, 0x04, 0xb7, 0xa2, 0x2c, 0xfa, 0x26, 0xac, 0xa0, 0x3e, 0x3f, 0xa6, 0x3d, 0x97, 0x0f, - 0x8d, 0xc5, 0xb0, 0xe6, 0x91, 0xa1, 0x5e, 0x13, 0x55, 0x8f, 0xd6, 0xa2, 0xec, 0x4a, 0xb2, 0xec, - 0x14, 0x45, 0xab, 0x0c, 0x9b, 0x59, 0xf6, 0xa8, 0x78, 0xeb, 0x42, 0x83, 0x42, 0x93, 0x91, 0xcf, - 0x29, 0xc7, 0xfa, 0xfb, 0x19, 0x42, 0x34, 0x8a, 0x7f, 0xbf, 0xac, 0x8c, 0x9b, 0x65, 0x4b, 0x8c, - 0xc9, 0xa3, 0xdb, 0x90, 0x1b, 0x50, 0x8e, 0x7b, 0x92, 0xf0, 0x8c, 0x5e, 0x90, 0x61, 0x7a, 0x0d, - 0xf2, 0x34, 0xe0, 0x2e, 0xf5, 0xc3, 0xe6, 0x59, 0xab, 0x99, 0x76, 0x52, 0x1b, 0x5b, 0x90, 0x79, - 0x14, 0x46, 0xb4, 0x54, 0xe4, 0xac, 0xe6, 0xa9, 0xdf, 0x16, 0xb2, 0xc8, 0xdc, 0x42, 0x12, 0x3d, - 0x29, 0x89, 0x48, 0x66, 0xad, 0xc3, 0x4d, 0xf5, 0x19, 0x17, 0xfe, 0xaf, 0x16, 0xdb, 0xbe, 0xc0, - 0x2e, 0x39, 0xe6, 0xb8, 0xfb, 0xaa, 0x04, 0xd8, 0x87, 0x82, 0x2c, 0x8b, 0x19, 0x4b, 0xe1, 0x18, - 0x5a, 0x93, 0x0a, 0x44, 0x8c, 0xc6, 0x94, 0x88, 0x20, 0x33, 0xa5, 0xd8, 0x49, 0x4a, 0x61, 0xa6, - 0xa5, 0x88, 0x32, 0x5b, 0x1b, 0x50, 0x9a, 0x30, 0x8d, 0xf7, 0x04, 0x34, 0x19, 0x89, 0xc6, 0xfd, - 0x9a, 0xaa, 0x7c, 0x00, 0x2b, 0xea, 0xb2, 0xa1, 0xf3, 0x95, 0x19, 0x85, 0xea, 0xfb, 0x90, 0x47, - 0x1e, 0xed, 0xfb, 0x5c, 0x89, 0x73, 0xb5, 0x3b, 0x4a, 0x61, 0xea, 0xdb, 0xe1, 0x8c, 0xc4, 0xd9, - 0x84, 0x0a, 0xb7, 0x92, 0x2a, 0xa8, 0xb2, 0xac, 0x22, 0xe8, 0xa3, 0x55, 0x5c, 0xfb, 0x33, 0xd9, - 0x16, 0x9f, 0x05, 0x5d, 0xc4, 0xf1, 0x63, 0xd4, 0x43, 0x1e, 0x13, 0x95, 0x8c, 0xa6, 0x52, 0x9b, - 0x57, 0x49, 0x1c, 0xaa, 0x7f, 0x08, 0xf9, 0x20, 0xcc, 0x10, 0x96, 0xbf, 0x5a, 0x7b, 0x63, 0xf2, - 0x98, 0x65, 0xfe, 0x44, 0x19, 0x12, 0x50, 0xbf, 0x97, 0x1e, 0xf5, 0xad, 0xa8, 0x8c, 0xd3, 0xe8, - 0x81, 0x9a, 0xe0, 0xa9, 0x8e, 0x74, 0xdc, 0x14, 0x97, 0xf5, 0x1d, 0xbc, 0xde, 0x64, 0x44, 0x5e, - 0x7d, 0xf8, 0x21, 0xfa, 0xf6, 0xba, 0x35, 0xd5, 0xab, 0x69, 0x62, 0xe5, 0x2c, 0x62, 0xa3, 0xad, - 0xac, 0x12, 0xdc, 0x4a, 0x18, 0x62, 0x52, 0xbf, 0x68, 0x50, 0x19, 0x79, 0x0e, 0xa9, 0xcf, 0xb8, - 0xcb, 0xfb, 0xa2, 0xcb, 0x0f, 0x3c, 0xec, 0x77, 0x3d, 0x71, 0x23, 0x5e, 0x97, 0xe7, 0xfd, 0x34, - 0xcf, 0x3b, 0x33, 0x78, 0xc6, 0x1b, 0x5a, 0x3b, 0x70, 0x77, 0x0e, 0xa7, 0x88, 0x7f, 0xed, 0x9f, - 0x1c, 0x2c, 0x35, 0x19, 0xd1, 0xbf, 0x86, 0xb5, 0x89, 0x27, 0xfa, 0xf6, 0xe4, 0x49, 0xa7, 0x5e, - 0x1f, 0x73, 0x67, 0x6e, 0x48, 0xfc, 0x40, 0x11, 0x58, 0x4f, 0xbf, 0x3d, 0x77, 0x32, 0xf0, 0xa9, - 0x28, 0xf3, 0x9d, 0xab, 0x44, 0xc5, 0x1b, 0x7d, 0x04, 0xcb, 0xe1, 0x43, 0x50, 0xca, 0x40, 0x09, - 0x87, 0x59, 0x99, 0xe2, 0x88, 0x33, 0x7c, 0x09, 0xaf, 0x25, 0x6e, 0xd4, 0x69, 0x80, 0x28, 0xc0, - 0xbc, 0x3b, 0x27, 0x20, 0xce, 0xfc, 0x00, 0x0a, 0xd1, 0x85, 0x64, 0x66, 0x60, 0x94, 0xcf, 0xb4, - 0xa6, 0xfb, 0xc6, 0x49, 0x26, 0xe6, 0x3b, 0x8b, 0xe4, 0x78, 0x40, 0x26, 0xc9, 0xac, 0x31, 0xd3, - 0x5b, 0x00, 0x63, 0x33, 0xf6, 0x56, 0x06, 0x6c, 0xe4, 0x36, 0xdf, 0x9e, 0xe9, 0x8e, 0x73, 0xfe, - 0xa8, 0xc1, 0xe6, 0xcc, 0x11, 0x71, 0xa6, 0xe7, 0xc9, 0x04, 0x98, 0xf7, 0xff, 0x27, 0x20, 0xa2, - 0x62, 0xe6, 0xbe, 0x17, 0x97, 0x54, 0xe3, 0xd3, 0xe7, 0xe7, 0x65, 0xed, 0xc5, 0x79, 0x59, 0xfb, - 0xeb, 0xbc, 0xac, 0xfd, 0x7c, 0x51, 0x5e, 0x78, 0x71, 0x51, 0x5e, 0xf8, 0xf3, 0xa2, 0xbc, 0xf0, - 0xd5, 0x1e, 0x71, 0xf9, 0x71, 0xbf, 0x6d, 0x77, 0xa8, 0xe7, 0xa8, 0x3d, 0xf6, 0x8e, 0xfb, 0x6d, - 0x27, 0x39, 0x79, 0x7c, 0x18, 0x60, 0x26, 0xfe, 0x81, 0xe7, 0xc3, 0xbf, 0x49, 0xf7, 0xfe, 0x0b, - 0x00, 0x00, 0xff, 0xff, 0x9d, 0xc2, 0x7b, 0xc3, 0xc3, 0x0b, 0x00, 0x00, + // 1026 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0x4d, 0x6f, 0x1b, 0x45, + 0x18, 0xce, 0xe6, 0xb3, 0x79, 0x03, 0xa9, 0xb2, 0x72, 0xf1, 0x66, 0x09, 0x76, 0xba, 0x2a, 0x6a, + 0x12, 0x91, 0x5d, 0xec, 0x02, 0x15, 0x56, 0x0e, 0xc4, 0x01, 0xa1, 0x4a, 0xb5, 0x5a, 0xb9, 0xe2, + 0x43, 0x1c, 0x88, 0xc6, 0xf6, 0x30, 0x59, 0x29, 0xbb, 0xb3, 0xf2, 0x8c, 0x4d, 0xcc, 0x09, 0x71, + 0x42, 0x9c, 0xf8, 0x19, 0x1c, 0x73, 0xe8, 0xa5, 0x7f, 0x00, 0x55, 0x9c, 0x2a, 0x4e, 0x9c, 0x2a, + 0x94, 0x1c, 0x22, 0x71, 0xe7, 0x88, 0x84, 0x66, 0x67, 0x66, 0xed, 0xf5, 0xae, 0xed, 0x92, 0x03, + 0x17, 0x6b, 0xe7, 0xfd, 0x9a, 0xe7, 0x79, 0x66, 0xde, 0x77, 0x0c, 0x45, 0xc4, 0x69, 0x40, 0x43, + 0xec, 0x11, 0xda, 0xf7, 0xfa, 0x15, 0x8f, 0x9f, 0xb9, 0x51, 0x97, 0x72, 0x6a, 0xae, 0x2b, 0x87, + 0x4b, 0x68, 0xdf, 0xed, 0x57, 0xec, 0x52, 0x9b, 0xb2, 0x80, 0x32, 0xaf, 0x85, 0x18, 0xf6, 0xfa, + 0x95, 0x16, 0xe6, 0xa8, 0xe2, 0xb5, 0xa9, 0x1f, 0xca, 0x78, 0xdb, 0x1a, 0x2b, 0x24, 0xd2, 0xa4, + 0xa7, 0x40, 0x28, 0xa1, 0xf1, 0xa7, 0x27, 0xbe, 0x94, 0x75, 0x53, 0xd6, 0x3b, 0x96, 0x0e, 0xb9, + 0xd0, 0x2e, 0x42, 0x29, 0x39, 0xc5, 0x5e, 0xbc, 0x6a, 0xf5, 0xbe, 0xf1, 0x50, 0x38, 0x50, 0xae, + 0xa2, 0x42, 0x11, 0x30, 0x22, 0x36, 0x09, 0x18, 0x51, 0x8e, 0x0d, 0x14, 0xf8, 0x21, 0xf5, 0xe2, + 0x5f, 0x69, 0x72, 0x7e, 0x9d, 0x87, 0x8d, 0x06, 0x23, 0x4f, 0x7a, 0xad, 0xc0, 0xe7, 0x8f, 0xbb, + 0x34, 0xa2, 0x0c, 0x9d, 0x9a, 0xef, 0xc2, 0x8d, 0x00, 0x33, 0x86, 0x08, 0x66, 0x96, 0xb1, 0xbd, + 0xb0, 0xb3, 0x56, 0x2d, 0xb8, 0x72, 0x3f, 0x57, 0xef, 0xe7, 0x1e, 0x86, 0x83, 0x66, 0x12, 0x65, + 0x36, 0xe0, 0xa6, 0x1f, 0xfa, 0xdc, 0x47, 0xa7, 0xc7, 0x1d, 0x1c, 0x51, 0xe6, 0x73, 0x6b, 0x3e, + 0x4e, 0xdc, 0x74, 0x15, 0x6c, 0xa1, 0x89, 0xab, 0x34, 0x71, 0x8f, 0xa8, 0x1f, 0xd6, 0x57, 0x9f, + 0xbf, 0x2c, 0xcf, 0xfd, 0x72, 0x75, 0xbe, 0x67, 0x34, 0xd7, 0x55, 0xf2, 0xc7, 0x32, 0xd7, 0x7c, + 0x0f, 0x6e, 0x44, 0x31, 0x18, 0xdc, 0xb5, 0x16, 0xb6, 0x8d, 0x9d, 0xd5, 0xba, 0xf5, 0xfb, 0xd3, + 0xfd, 0x82, 0x2a, 0x75, 0xd8, 0xe9, 0x74, 0x31, 0x63, 0x4f, 0x78, 0xd7, 0x0f, 0x49, 0x33, 0x89, + 0x34, 0x6d, 0x01, 0x9b, 0xa3, 0x0e, 0xe2, 0xc8, 0x5a, 0x14, 0x59, 0xcd, 0x64, 0x6d, 0x16, 0x60, + 0x89, 0xfb, 0xfc, 0x14, 0x5b, 0x4b, 0xb1, 0x43, 0x2e, 0x4c, 0x0b, 0x56, 0x58, 0x2f, 0x08, 0x50, + 0x77, 0x60, 0x2d, 0xc7, 0x76, 0xbd, 0xac, 0xb9, 0x3f, 0x5c, 0x9d, 0xef, 0x25, 0xa5, 0x7f, 0xba, + 0x3a, 0xdf, 0xdb, 0xd2, 0x87, 0xd7, 0xaf, 0x78, 0x19, 0xc9, 0x9c, 0x03, 0xd8, 0xcc, 0x18, 0x9b, + 0x98, 0x45, 0x34, 0x64, 0xd8, 0x2c, 0xc3, 0x5a, 0xa4, 0x6c, 0xc7, 0x7e, 0xc7, 0x32, 0xb6, 0x8d, + 0x9d, 0xc5, 0x26, 0x68, 0xd3, 0x83, 0x8e, 0xf3, 0xcc, 0x80, 0x42, 0x83, 0x91, 0x4f, 0xce, 0x70, + 0xfb, 0x21, 0x26, 0xa8, 0x3d, 0x38, 0xa2, 0x21, 0xc7, 0x21, 0x37, 0x1f, 0xc1, 0x4a, 0x5b, 0x7e, + 0xc6, 0x59, 0x13, 0x0e, 0xa2, 0x5e, 0xfe, 0xed, 0xe9, 0xfe, 0x9b, 0xe9, 0xcb, 0xa8, 0x85, 0x8e, + 0x93, 0x9b, 0xba, 0x8a, 0xb9, 0x05, 0xab, 0xa8, 0xc7, 0x4f, 0x68, 0xd7, 0xe7, 0x03, 0x6b, 0x3e, + 0xe6, 0x3c, 0x34, 0xd4, 0xaa, 0x82, 0xf5, 0x70, 0x2d, 0x68, 0x97, 0xd3, 0xb4, 0x33, 0x10, 0x9d, + 0x12, 0x6c, 0xe5, 0xd9, 0x35, 0x79, 0xe7, 0xd2, 0x80, 0x95, 0x06, 0x23, 0x9f, 0x53, 0x8e, 0xcd, + 0xf7, 0x73, 0x84, 0xa8, 0x17, 0xfe, 0x7a, 0x59, 0x1e, 0x35, 0xcb, 0x2b, 0x31, 0x22, 0x8f, 0xe9, + 0xc2, 0x52, 0x9f, 0x72, 0xdc, 0x95, 0x80, 0xa7, 0xdc, 0x05, 0x19, 0x66, 0x56, 0x61, 0x99, 0x46, + 0xdc, 0xa7, 0x61, 0x7c, 0x79, 0xd6, 0xab, 0xb6, 0x9b, 0xd6, 0xc6, 0x15, 0x60, 0x1e, 0xc5, 0x11, + 0x4d, 0x15, 0x39, 0xed, 0xf2, 0xd4, 0x6e, 0x0b, 0x59, 0x64, 0x6d, 0x21, 0x89, 0x99, 0x96, 0x44, + 0x14, 0x73, 0x36, 0xe0, 0xa6, 0xfa, 0x4c, 0x88, 0xff, 0x63, 0x24, 0xb6, 0x2f, 0xb0, 0x4f, 0x4e, + 0x38, 0xee, 0xfc, 0x5f, 0x02, 0x1c, 0xc0, 0x8a, 0xa4, 0xc5, 0xac, 0x85, 0xb8, 0x0d, 0x9d, 0x71, + 0x05, 0x34, 0xa2, 0x11, 0x25, 0x74, 0xca, 0x54, 0x29, 0x76, 0xd3, 0x52, 0xd8, 0x59, 0x29, 0x74, + 0x65, 0x67, 0x13, 0x8a, 0x63, 0xa6, 0xd1, 0x3b, 0x01, 0x0d, 0x46, 0x74, 0xbb, 0x5f, 0x53, 0x95, + 0x0f, 0x60, 0x55, 0x0d, 0x1b, 0x3a, 0x5b, 0x99, 0x61, 0xa8, 0x79, 0x00, 0xcb, 0x28, 0xa0, 0xbd, + 0x90, 0x2b, 0x71, 0x5e, 0x6d, 0x46, 0xa9, 0x9c, 0xda, 0x4e, 0xdc, 0x23, 0x49, 0x35, 0xa1, 0xc2, + 0xad, 0xb4, 0x0a, 0x8a, 0x96, 0x53, 0x00, 0x73, 0xb8, 0x4a, 0xb8, 0x3f, 0x93, 0xd7, 0xe2, 0xb3, + 0xa8, 0x83, 0x38, 0x7e, 0x8c, 0xba, 0x28, 0x60, 0x82, 0xc9, 0xb0, 0x2b, 0x8d, 0x59, 0x4c, 0x92, + 0x50, 0xf3, 0x43, 0x58, 0x8e, 0xe2, 0x0a, 0x31, 0xfd, 0xb5, 0xea, 0x1b, 0xe3, 0xc7, 0x2c, 0xeb, + 0xa7, 0x68, 0xc8, 0x84, 0xda, 0xbd, 0x6c, 0xab, 0x6f, 0x6b, 0x1a, 0x67, 0xfa, 0x81, 0x1a, 0xc3, + 0xa9, 0x8e, 0x74, 0xd4, 0x94, 0xd0, 0xfa, 0x0e, 0x5e, 0x6f, 0x30, 0x22, 0x47, 0x1f, 0x7e, 0x88, + 0xbe, 0xbd, 0x2e, 0xa7, 0x5a, 0x25, 0x0b, 0xac, 0x94, 0x07, 0x6c, 0xb8, 0x95, 0x53, 0x84, 0x5b, + 0x29, 0x43, 0x02, 0xea, 0xdc, 0x80, 0xf2, 0xd0, 0x73, 0x44, 0x43, 0xc6, 0x7d, 0xde, 0x13, 0xb7, + 0xfc, 0x30, 0xc0, 0x61, 0x27, 0x10, 0x13, 0xf1, 0xba, 0xda, 0x8b, 0x49, 0xaa, 0x8b, 0x24, 0x93, + 0x54, 0x1b, 0x6a, 0xf7, 0xb3, 0x2c, 0xee, 0x4c, 0x61, 0x91, 0xc0, 0x71, 0x76, 0xe1, 0xee, 0x0c, + 0xc4, 0x9a, 0x5d, 0xf5, 0xef, 0x25, 0x58, 0x68, 0x30, 0x62, 0x7e, 0x0d, 0xeb, 0x63, 0x0f, 0xf8, + 0xed, 0xf1, 0x7b, 0x90, 0x79, 0x9b, 0xec, 0xdd, 0x99, 0x21, 0xc9, 0xf3, 0x45, 0x60, 0x23, 0xfb, + 0x32, 0xdd, 0xc9, 0xc9, 0xcf, 0x44, 0xd9, 0xef, 0xbc, 0x4a, 0x54, 0xb2, 0xd1, 0x47, 0xb0, 0x18, + 0x3f, 0x13, 0xc5, 0x9c, 0x2c, 0xe1, 0xb0, 0xcb, 0x13, 0x1c, 0x49, 0x85, 0x2f, 0xe1, 0xb5, 0xd4, + 0xbc, 0x9d, 0x94, 0xa0, 0x03, 0xec, 0xbb, 0x33, 0x02, 0x92, 0xca, 0x0f, 0x60, 0x45, 0x8f, 0x2b, + 0x3b, 0x27, 0x47, 0xf9, 0x6c, 0x67, 0xb2, 0x6f, 0x14, 0x64, 0xaa, 0xfb, 0xf3, 0x40, 0x8e, 0x06, + 0xe4, 0x82, 0xcc, 0x6b, 0x42, 0xb3, 0x09, 0x30, 0xd2, 0x81, 0x6f, 0xe5, 0xa4, 0x0d, 0xdd, 0xf6, + 0xdb, 0x53, 0xdd, 0x49, 0xcd, 0x1f, 0x0d, 0xd8, 0x9a, 0xda, 0x40, 0xde, 0xe4, 0x3a, 0xb9, 0x09, + 0xf6, 0xfd, 0xff, 0x98, 0xa0, 0xa1, 0xd8, 0x4b, 0xdf, 0x8b, 0x11, 0x56, 0xff, 0xf4, 0xf9, 0x45, + 0xc9, 0x78, 0x71, 0x51, 0x32, 0xfe, 0xbc, 0x28, 0x19, 0x3f, 0x5f, 0x96, 0xe6, 0x5e, 0x5c, 0x96, + 0xe6, 0xfe, 0xb8, 0x2c, 0xcd, 0x7d, 0xb5, 0x4f, 0x7c, 0x7e, 0xd2, 0x6b, 0xb9, 0x6d, 0x1a, 0x78, + 0x6a, 0x8f, 0xfd, 0x93, 0x5e, 0xcb, 0x4b, 0x77, 0x1e, 0x1f, 0x44, 0x98, 0x89, 0xff, 0xe7, 0xcb, + 0xf1, 0x9f, 0xa8, 0x7b, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xec, 0xe9, 0xac, 0xe1, 0x0b, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1842,6 +1852,13 @@ func (m *MsgProposeConstitutionAmendment) MarshalToSizedBuffer(dAtA []byte) (int _ = i var l int _ = l + if len(m.Amendment) > 0 { + i -= len(m.Amendment) + copy(dAtA[i:], m.Amendment) + i = encodeVarintTx(dAtA, i, uint64(len(m.Amendment))) + i-- + dAtA[i] = 0x12 + } if len(m.Authority) > 0 { i -= len(m.Authority) copy(dAtA[i:], m.Authority) @@ -2115,6 +2132,10 @@ func (m *MsgProposeConstitutionAmendment) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + l = len(m.Amendment) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -3578,6 +3599,38 @@ func (m *MsgProposeConstitutionAmendment) Unmarshal(dAtA []byte) error { } m.Authority = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amendment", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Amendment = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) From f8efeac365b8af185414d1e4864472aeab112e28 Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:07:11 +0200 Subject: [PATCH 02/11] add cli cmd to generate amendment --- x/gov/client/cli/query.go | 59 ++++++++++++++++++++++++++++++++ x/gov/client/cli/query_test.go | 29 ++++++++++++++++ x/gov/client/cli/util.go | 27 +++++++++++++++ x/gov/keeper/constitution.go | 1 - x/gov/keeper/msg_server_test.go | 11 ++++-- x/gov/types/unified_diff.go | 16 ++++----- x/gov/types/unified_diff_test.go | 27 +++++++++++++-- 7 files changed, 155 insertions(+), 15 deletions(-) diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index 95e3de6ec..4feed538b 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" gcutils "github.com/atomone-hub/atomone/x/gov/client/utils" "github.com/atomone-hub/atomone/x/gov/types" @@ -40,6 +41,7 @@ func GetQueryCmd() *cobra.Command { GetCmdQueryDeposits(), GetCmdQueryTally(), GetCmdConstitution(), + GetCmdGenerateConstitutionAmendment(), ) return govQueryCmd @@ -676,3 +678,60 @@ func GetCmdConstitution() *cobra.Command { }, } } + +// GetCmdConstitutionAmendmentMsg 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 GetCmdGenerateConstitutionAmendment() *cobra.Command { + 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 and generates the valid sdk.Msg for a +constitution amendment proposal containing the unified diff between the +current constitution and the updated constitution from the provided markdown file. + +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 + } + + // Query the current constitution + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + queryClient := v1.NewQueryClient(clientCtx) + resp, err := queryClient.Constitution(cmd.Context(), &v1.QueryConstitutionRequest{}) + if err != nil { + return err + } + + // Generate the unified diff between the current and updated constitutions + diff, err := types.GenerateUnifiedDiff(resp.Constitution, 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) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/gov/client/cli/query_test.go b/x/gov/client/cli/query_test.go index 68bd901cf..96808f5ab 100644 --- a/x/gov/client/cli/query_test.go +++ b/x/gov/client/cli/query_test.go @@ -406,3 +406,32 @@ func (s *CLITestSuite) TestCmdGetConstitution() { }) } } + +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.GetCmdGenerateConstitutionAmendment() + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) + s.Require().NoError(err) + s.Require().Contains(out.String(), tc.expCmdOutput) + }) + } +} diff --git a/x/gov/client/cli/util.go b/x/gov/client/cli/util.go index 15dfec1e1..f357c5a75 100644 --- a/x/gov/client/cli/util.go +++ b/x/gov/client/cli/util.go @@ -170,3 +170,30 @@ func ReadGovPropFlags(clientCtx client.Context, flagSet *pflag.FlagSet) (*govv1. return rv, nil } + +// readFromMarkdownFile reads the contents of a markdown file +// and returns it as a string. +func readFromMarkdownFile(filePath string) (string, error) { + file, err := os.Open(filePath) + if err != nil { + return "", fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + stat, err := file.Stat() + if err != nil { + return "", fmt.Errorf("failed to get file info: %w", err) + } + + if stat.Size() == 0 { + return "", fmt.Errorf("file is empty") + } + + contents := make([]byte, stat.Size()) + _, err = file.Read(contents) + if err != nil { + return "", fmt.Errorf("failed to read file: %w", err) + } + + return string(contents), nil +} diff --git a/x/gov/keeper/constitution.go b/x/gov/keeper/constitution.go index 15618438f..0c133e692 100644 --- a/x/gov/keeper/constitution.go +++ b/x/gov/keeper/constitution.go @@ -27,7 +27,6 @@ func (k Keeper) ApplyConstitutionAmendment(ctx sdk.Context, amendment string) (u currentConstitution := k.GetConstitution(ctx) updatedConstitution, err = types.ApplyUnifiedDiff(currentConstitution, amendment) - if err != nil { return "", types.ErrInvalidConstitutionAmendment.Wrapf("failed to apply amendment: %v", err) } diff --git a/x/gov/keeper/msg_server_test.go b/x/gov/keeper/msg_server_test.go index 5d5b8041b..a04c7f1ce 100644 --- a/x/gov/keeper/msg_server_test.go +++ b/x/gov/keeper/msg_server_test.go @@ -1304,7 +1304,7 @@ func (suite *KeeperTestSuite) TestSubmitProposal_InitialDeposit() { func (suite *KeeperTestSuite) TestProposeConstitutionAmendment() { ctx := suite.ctx - suite.govKeeper.SetConstitution(ctx, "Hello World") + suite.govKeeper.SetConstitution(ctx, "Hello World") cases := map[string]struct { msg *v1.MsgProposeConstitutionAmendment @@ -1315,12 +1315,19 @@ func (suite *KeeperTestSuite) TestProposeConstitutionAmendment() { "successful amendment": { msg: v1.NewMsgProposeConstitutionAmendment( suite.govKeeper.GetGovernanceAccount(ctx).GetAddress(), - "@@ -1 +1 @@\n-Hello World\n+Hi World", + "@@ -1 +1 @@\n-Hello World\n+Hi World", ), expErr: false, expResult: "Hi World", }, "failed amendment": { + msg: v1.NewMsgProposeConstitutionAmendment( + suite.govKeeper.GetGovernanceAccount(ctx).GetAddress(), + "@@ -1 +1 @@\n-Hello World\n+Hi World", + ), + expErr: true, + }, + "invalid patch": { msg: v1.NewMsgProposeConstitutionAmendment( suite.govKeeper.GetGovernanceAccount(ctx).GetAddress(), "invalid patch", diff --git a/x/gov/types/unified_diff.go b/x/gov/types/unified_diff.go index fa7a9aa3e..78a2be16e 100644 --- a/x/gov/types/unified_diff.go +++ b/x/gov/types/unified_diff.go @@ -276,22 +276,18 @@ func ApplyUnifiedDiff(src, diffStr string) (string, error) { // This is the only function that uses the gotextdiff library as its primary use is for // clients. func GenerateUnifiedDiff(src, dst string) (string, error) { - if src == "" || dst == "" { - return "", fmt.Errorf("source and destination strings cannot be empty") - } - // Create spans for the source and destination texts srcURI := span.URIFromPath("src") - // add an EOL to src and dst if they don't have one - if src[len(src)-1] != '\n' { - src += "\n" + + if src == "" || src[len(src)-1] != '\n' { + src += "\n" // Add an EOL to src if it's empty or newline is missing } - if dst[len(dst)-1] != '\n' { - dst += "\n" + if dst == "" || dst[len(dst)-1] != '\n' { + dst += "\n" // Add an EOL to dst if it's empty or newline is missing } // Compute the edits using the Myers diff algorithm - eds := myers.ComputeEdits(span.URI(srcURI), src, dst) + eds := myers.ComputeEdits(srcURI, src, dst) // Generate the unified diff string diff := gotextdiff.ToUnified("src", "dst", src, eds) diff --git a/x/gov/types/unified_diff_test.go b/x/gov/types/unified_diff_test.go index 25120f4dd..4828c2c2e 100644 --- a/x/gov/types/unified_diff_test.go +++ b/x/gov/types/unified_diff_test.go @@ -89,7 +89,9 @@ func TestApplyUnifiedDiff(t *testing.T) { { name: "Apply addition", src: "Line one\nLine two", - diffStr: `@@ -1,2 +1,3 @@ + diffStr: `--- src ++++ dst +@@ -1,2 +1,3 @@ Line one Line two +Line three @@ -111,7 +113,9 @@ func TestApplyUnifiedDiff(t *testing.T) { { name: "Apply modification", src: "Line one\nLine two\nLine three", - diffStr: `@@ -2 +2 @@ + diffStr: `--- a ++++ b +@@ -2 +2 @@ -Line two +Line two modified `, @@ -131,6 +135,25 @@ func TestApplyUnifiedDiff(t *testing.T) { expected: "Line zero\nLine one\nLine three\nLine four", wantErr: false, }, + { + name: "Apply multiple hunks", + src: "Line one\nLine two\nLine three\nLine four\nLine five\nLine six\nLine seven\nLine eight\nLine nine", + diffStr: `@@ -1,4 +1,4 @@ +-Line one ++Line one modified + Line two + Line three + Line four +@@ -6,4 +6,4 @@ + Line six + Line seven + Line eight +-Line nine ++Line nine modified +`, + expected: "Line one modified\nLine two\nLine three\nLine four\nLine five\nLine six\nLine seven\nLine eight\nLine nine modified", + wantErr: false, + }, { name: "Malformed diff", src: "Line one\nLine two", From fa4aa5f1911ab9544e23ae3823dce39c428bb2ae Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:36:19 +0200 Subject: [PATCH 03/11] move generate amendment cli cmd to txs --- x/gov/client/cli/query.go | 58 --------------------------------- x/gov/client/cli/query_test.go | 29 ----------------- x/gov/client/cli/tx.go | 59 ++++++++++++++++++++++++++++++++++ x/gov/client/cli/tx_test.go | 29 +++++++++++++++++ 4 files changed, 88 insertions(+), 87 deletions(-) diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index 4feed538b..45a8906db 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -11,7 +11,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" gcutils "github.com/atomone-hub/atomone/x/gov/client/utils" "github.com/atomone-hub/atomone/x/gov/types" @@ -678,60 +677,3 @@ func GetCmdConstitution() *cobra.Command { }, } } - -// GetCmdConstitutionAmendmentMsg 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 GetCmdGenerateConstitutionAmendment() *cobra.Command { - 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 and generates the valid sdk.Msg for a -constitution amendment proposal containing the unified diff between the -current constitution and the updated constitution from the provided markdown file. - -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 - } - - // Query the current constitution - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - queryClient := v1.NewQueryClient(clientCtx) - resp, err := queryClient.Constitution(cmd.Context(), &v1.QueryConstitutionRequest{}) - if err != nil { - return err - } - - // Generate the unified diff between the current and updated constitutions - diff, err := types.GenerateUnifiedDiff(resp.Constitution, 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) - }, - } - - flags.AddTxFlagsToCmd(cmd) - - return cmd -} diff --git a/x/gov/client/cli/query_test.go b/x/gov/client/cli/query_test.go index 96808f5ab..68bd901cf 100644 --- a/x/gov/client/cli/query_test.go +++ b/x/gov/client/cli/query_test.go @@ -406,32 +406,3 @@ func (s *CLITestSuite) TestCmdGetConstitution() { }) } } - -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.GetCmdGenerateConstitutionAmendment() - out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) - s.Require().NoError(err) - s.Require().Contains(out.String(), tc.expCmdOutput) - }) - } -} diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index f9f98a6b7..b790b8e5e 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -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" @@ -375,3 +376,61 @@ $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.1 --from mykey return cmd } + +// GetCmdConstitutionAmendmentMsg 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 GetCmdGenerateConstitutionAmendment() *cobra.Command { + 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 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. + +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 + } + + // Query the current constitution + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + queryClient := v1.NewQueryClient(clientCtx) + resp, err := queryClient.Constitution(cmd.Context(), &v1.QueryConstitutionRequest{}) + if err != nil { + return err + } + + // Generate the unified diff between the current and updated constitutions + diff, err := types.GenerateUnifiedDiff(resp.Constitution, 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) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/gov/client/cli/tx_test.go b/x/gov/client/cli/tx_test.go index 7772362ec..6d3be639f 100644 --- a/x/gov/client/cli/tx_test.go +++ b/x/gov/client/cli/tx_test.go @@ -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.GetCmdGenerateConstitutionAmendment() + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) + s.Require().NoError(err) + s.Require().Contains(out.String(), tc.expCmdOutput) + }) + } +} From 520714ab90be52e161a522e1714b2c2d9006566f Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:39:16 +0200 Subject: [PATCH 04/11] simulate amendments but with empty amendment --- tests/e2e/e2e_gov_test.go | 25 ++++++++++++++++++++++++ tests/e2e/e2e_setup_test.go | 33 ++++++++++++++++++++++++++++---- tests/e2e/e2e_test.go | 1 + tests/e2e/query_test.go | 10 ++++++++++ x/gov/simulation/genesis_test.go | 1 + x/gov/simulation/proposals.go | 6 +++--- x/gov/types/unified_diff_test.go | 32 ++++++++++++++++++++++++++++--- 7 files changed, 98 insertions(+), 10 deletions(-) diff --git a/tests/e2e/e2e_gov_test.go b/tests/e2e/e2e_gov_test.go index c7f0f0190..7ddca472a 100644 --- a/tests/e2e/e2e_gov_test.go +++ b/tests/e2e/e2e_gov_test.go @@ -176,6 +176,31 @@ 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() + + s.writeGovConstitutionAmendmentProposal(s.chainA) + // 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 != "" // constitution should remain an empty string + }, + 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 diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 6aad487c0..92d81a5a7 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -61,10 +61,11 @@ 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" // hermesBinary = "hermes" // hermesConfigWithGasPrices = "/root/.hermes/config.toml" @@ -625,6 +626,30 @@ func (s *IntegrationTestSuite) writeGovCommunitySpendProposal(c *chain, amount s s.Require().NoError(err) } +func (s *IntegrationTestSuite) writeGovConstitutionAmendmentProposal(c *chain) { + govModuleAddress := authtypes.NewModuleAddress(govtypes.ModuleName).String() + emptyAmendment := "@@ -1 +1 @@\n-\n+\n" // valid diff for empty constitution with no changes + 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, emptyAmendment) + err := writeFile(filepath.Join(c.validators[0].configDir(), "config", proposalConstitutionAmendmentFilename), []byte(propMsgBody)) + s.Require().NoError(err) +} + func configFile(filename string) string { filepath := filepath.Join(atomoneConfigPath, filename) return filepath diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 04cc39eb7..a42a68e96 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -58,6 +58,7 @@ func (s *IntegrationTestSuite) TestGov() { s.testGovCancelSoftwareUpgrade() s.testGovCommunityPoolSpend() s.testGovParamChange() + s.testGovConstitutionAmendment() } func (s *IntegrationTestSuite) TestSlashing() { diff --git a/tests/e2e/query_test.go b/tests/e2e/query_test.go index 236232bf7..f2e31bc75 100644 --- a/tests/e2e/query_test.go +++ b/tests/e2e/query_test.go @@ -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" ) @@ -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/v1beta1/constitution", endpoint)) + s.Require().NoError(err) + err = cdc.UnmarshalJSON(body, &res) + s.Require().NoError(err) + return res +} diff --git a/x/gov/simulation/genesis_test.go b/x/gov/simulation/genesis_test.go index 0ffeda598..7b42943b5 100644 --- a/x/gov/simulation/genesis_test.go +++ b/x/gov/simulation/genesis_test.go @@ -70,6 +70,7 @@ func TestRandomizedGenState(t *testing.T) { require.Equal(t, []*v1.Deposit{}, govGenesis.Deposits) require.Equal(t, []*v1.Vote{}, govGenesis.Votes) require.Equal(t, []*v1.Proposal{}, govGenesis.Proposals) + require.Equal(t, "", govGenesis.Constitution) } // TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState. diff --git a/x/gov/simulation/proposals.go b/x/gov/simulation/proposals.go index 2d23293dc..a6a0aa8d3 100644 --- a/x/gov/simulation/proposals.go +++ b/x/gov/simulation/proposals.go @@ -73,9 +73,9 @@ func SimulateLegacyTextProposalContent(r *rand.Rand, _ sdk.Context, _ []simtypes } // SimulateConstitutionAmendmentProposal returns a random constitution amendment proposal. -func SimulateConstitutionAmendmentProposal(r *rand.Rand, ctx sdk.Context, _ []simtypes.Account) sdk.Msg { - amendment := simtypes.RandStringOfLength(r, 140) - return v1.NewMsgProposeConstitutionAmendment(authtypes.NewModuleAddress(govtypes.ModuleName), amendment) +func SimulateConstitutionAmendmentProposal(_ *rand.Rand, ctx sdk.Context, _ []simtypes.Account) sdk.Msg { + emptyAmendment := "@@ -1 +1 @@\n-\n+\n" // valid diff with no changes + return v1.NewMsgProposeConstitutionAmendment(authtypes.NewModuleAddress(govtypes.ModuleName), emptyAmendment) } // SimulateLawProposal returns a random law proposal. diff --git a/x/gov/types/unified_diff_test.go b/x/gov/types/unified_diff_test.go index 4828c2c2e..349e2872d 100644 --- a/x/gov/types/unified_diff_test.go +++ b/x/gov/types/unified_diff_test.go @@ -175,11 +175,37 @@ func TestApplyUnifiedDiff(t *testing.T) { wantErr: true, }, { - name: "Empty diff", - src: "Line one\nLine two", - diffStr: ``, + name: "Empty diff", + src: "Line one\nLine two", + diffStr: `--- src ++++ dst +`, wantErr: true, }, + { + name: "Empty source", + src: "", + diffStr: `--- src ++++ dst +@@ -1 +1 @@ +- ++Line one +`, + expected: "Line one", + wantErr: false, + }, + { + name: "Empty source and no-op dst", + src: "", + diffStr: `--- src ++++ dst +@@ -1 +1 @@ +- ++ +`, + expected: "", + wantErr: false, + }, } for _, tt := range tests { From c460b814deb52c1b27cb5182f10a1b8eb574598d Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:04:03 +0200 Subject: [PATCH 05/11] fix e2e --- proto/atomone/gov/v1/query.proto | 2 +- tests/e2e/e2e_gov_test.go | 34 +++++++- tests/e2e/e2e_setup_test.go | 24 ------ tests/e2e/genesis.go | 1 + tests/e2e/query_test.go | 2 +- x/gov/types/v1/query.pb.go | 132 +++++++++++++++---------------- x/gov/types/v1/query.pb.gw.go | 2 +- 7 files changed, 102 insertions(+), 95 deletions(-) diff --git a/proto/atomone/gov/v1/query.proto b/proto/atomone/gov/v1/query.proto index 59da0c67b..e8e595cdf 100644 --- a/proto/atomone/gov/v1/query.proto +++ b/proto/atomone/gov/v1/query.proto @@ -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. diff --git a/tests/e2e/e2e_gov_test.go b/tests/e2e/e2e_gov_test.go index 7ddca472a..b2a7e9c6e 100644 --- a/tests/e2e/e2e_gov_test.go +++ b/tests/e2e/e2e_gov_test.go @@ -4,6 +4,7 @@ import ( "fmt" "path/filepath" "strconv" + "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -182,7 +183,11 @@ func (s *IntegrationTestSuite) testGovConstitutionAmendment() { senderAddress, _ := s.chainA.validators[0].keyInfo.GetAddress() sender := senderAddress.String() - s.writeGovConstitutionAmendmentProposal(s.chainA) + res := s.queryConstitution(chainAAPIEndpoint) + newConstitution := "New test constitution" + amendment, err := govtypes.GenerateUnifiedDiff(res.Constitution, newConstitution) + s.Require().NoError(err) + s.writeGovConstitutionAmendmentProposal(s.chainA, 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)} @@ -193,7 +198,7 @@ func (s *IntegrationTestSuite) testGovConstitutionAmendment() { s.Require().Eventually( func() bool { res := s.queryConstitution(chainAAPIEndpoint) - return res.Constitution != "" // constitution should remain an empty string + return res.Constitution == newConstitution }, 10*time.Second, time.Second, @@ -308,3 +313,28 @@ 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) +} diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 92d81a5a7..1fc233c37 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -626,30 +626,6 @@ func (s *IntegrationTestSuite) writeGovCommunitySpendProposal(c *chain, amount s s.Require().NoError(err) } -func (s *IntegrationTestSuite) writeGovConstitutionAmendmentProposal(c *chain) { - govModuleAddress := authtypes.NewModuleAddress(govtypes.ModuleName).String() - emptyAmendment := "@@ -1 +1 @@\n-\n+\n" // valid diff for empty constitution with no changes - 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, emptyAmendment) - err := writeFile(filepath.Join(c.validators[0].configDir(), "config", proposalConstitutionAmendmentFilename), []byte(propMsgBody)) - s.Require().NoError(err) -} - func configFile(filename string) string { filepath := filepath.Join(atomoneConfigPath, filename) return filepath diff --git a/tests/e2e/genesis.go b/tests/e2e/genesis.go index 50fe13d77..2e034d573 100644 --- a/tests/e2e/genesis.go +++ b/tests/e2e/genesis.go @@ -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) diff --git a/tests/e2e/query_test.go b/tests/e2e/query_test.go index f2e31bc75..de8288f6d 100644 --- a/tests/e2e/query_test.go +++ b/tests/e2e/query_test.go @@ -272,7 +272,7 @@ func (s *IntegrationTestSuite) queryStakingParams(endpoint string) stakingtypes. func (s *IntegrationTestSuite) queryConstitution(endpoint string) govtypesv1.QueryConstitutionResponse { var res govtypesv1.QueryConstitutionResponse - body, err := httpGet(fmt.Sprintf("%s/atomone/gov/v1beta1/constitution", endpoint)) + body, err := httpGet(fmt.Sprintf("%s/atomone/gov/v1/constitution", endpoint)) s.Require().NoError(err) err = cdc.UnmarshalJSON(body, &res) s.Require().NoError(err) diff --git a/x/gov/types/v1/query.pb.go b/x/gov/types/v1/query.pb.go index 884a45380..83c14285a 100644 --- a/x/gov/types/v1/query.pb.go +++ b/x/gov/types/v1/query.pb.go @@ -999,72 +999,72 @@ func init() { func init() { proto.RegisterFile("atomone/gov/v1/query.proto", fileDescriptor_2290d0188dd70223) } var fileDescriptor_2290d0188dd70223 = []byte{ - // 1033 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x6f, 0xdc, 0x44, - 0x10, 0x8f, 0x2f, 0x7f, 0x9a, 0x9b, 0xa4, 0x01, 0x86, 0xd0, 0x3a, 0x4e, 0x7b, 0xb4, 0x6e, 0x48, - 0xd2, 0x8a, 0xd8, 0x5c, 0x4a, 0x5a, 0x84, 0x28, 0x88, 0x50, 0x1a, 0xfa, 0x80, 0x14, 0xdc, 0x8a, - 0x07, 0x5e, 0x22, 0x27, 0x67, 0xb9, 0x96, 0x2e, 0x5e, 0xf7, 0x76, 0xef, 0x44, 0x14, 0x4e, 0x95, - 0x90, 0x90, 0x80, 0xa7, 0x22, 0x84, 0x10, 0xfd, 0x1c, 0x7c, 0x08, 0x1e, 0x2b, 0x78, 0xe1, 0x11, - 0x25, 0x7c, 0x0d, 0x24, 0xe4, 0xdd, 0xb1, 0xcf, 0x76, 0x7c, 0x7f, 0x52, 0x45, 0x7d, 0xf4, 0xee, - 0x6f, 0x7e, 0xf3, 0x9b, 0xd9, 0xd9, 0x99, 0x35, 0x18, 0xae, 0x60, 0xfb, 0x2c, 0xf4, 0x6c, 0x9f, - 0x75, 0xec, 0x4e, 0xdd, 0x7e, 0xdc, 0xf6, 0x5a, 0x07, 0x56, 0xd4, 0x62, 0x82, 0xe1, 0x1c, 0xed, - 0x59, 0x3e, 0xeb, 0x58, 0x9d, 0xba, 0x71, 0x63, 0x8f, 0xf1, 0x7d, 0xc6, 0xed, 0x5d, 0x97, 0x7b, - 0x0a, 0x68, 0x77, 0xea, 0xbb, 0x9e, 0x70, 0xeb, 0x76, 0xe4, 0xfa, 0x41, 0xe8, 0x8a, 0x80, 0x85, - 0xca, 0xd6, 0xb8, 0xe4, 0x33, 0xe6, 0x37, 0x3d, 0xdb, 0x8d, 0x02, 0xdb, 0x0d, 0x43, 0x26, 0xe4, - 0x26, 0xa7, 0x5d, 0xbd, 0xe0, 0x35, 0x76, 0xa0, 0x76, 0x16, 0x94, 0x8f, 0x1d, 0xf9, 0x65, 0xab, - 0x0f, 0xb5, 0x65, 0x1a, 0xa0, 0x7f, 0x11, 0x3b, 0xfd, 0x84, 0x85, 0x5c, 0x04, 0xa2, 0x1d, 0x13, - 0x3a, 0xde, 0xe3, 0xb6, 0xc7, 0x85, 0xf9, 0x11, 0x2c, 0x94, 0xec, 0xf1, 0x88, 0x85, 0xdc, 0x43, - 0x13, 0x66, 0xf7, 0x32, 0xeb, 0xba, 0x76, 0x45, 0x5b, 0xad, 0x3a, 0xb9, 0x35, 0xf3, 0x36, 0xcc, - 0x4b, 0x82, 0xed, 0x16, 0x8b, 0x18, 0x77, 0x9b, 0x44, 0x8c, 0x6f, 0xc2, 0x4c, 0x44, 0x4b, 0x3b, - 0x41, 0x43, 0x9a, 0x4e, 0x38, 0x90, 0x2c, 0xdd, 0x6f, 0x98, 0x9f, 0xc3, 0x1b, 0x05, 0x43, 0xf2, - 0xfa, 0x2e, 0x4c, 0x27, 0x30, 0x69, 0x36, 0xb3, 0xae, 0x5b, 0xf9, 0x84, 0x5a, 0xa9, 0x4d, 0x8a, - 0x34, 0x9f, 0x56, 0x0a, 0x7c, 0x3c, 0x51, 0xb2, 0x05, 0xaf, 0xa4, 0x4a, 0xb8, 0x70, 0x45, 0x9b, - 0x4b, 0xda, 0xb9, 0xf5, 0x5a, 0x3f, 0xda, 0x07, 0x12, 0xe5, 0xcc, 0x45, 0xb9, 0x6f, 0xb4, 0x60, - 0xb2, 0xc3, 0x84, 0xd7, 0xd2, 0x2b, 0x71, 0x1e, 0x36, 0xf5, 0x3f, 0x7f, 0x5f, 0x9b, 0xa7, 0x44, - 0x7f, 0xdc, 0x68, 0xb4, 0x3c, 0xce, 0x1f, 0x88, 0x56, 0x10, 0xfa, 0x8e, 0x82, 0xe1, 0x2d, 0xa8, - 0x36, 0xbc, 0x88, 0xf1, 0x40, 0xb0, 0x96, 0x3e, 0x3e, 0xc4, 0xa6, 0x07, 0xc5, 0x7b, 0x00, 0xbd, - 0xb2, 0xd0, 0x27, 0x64, 0x0a, 0x96, 0x2d, 0xb2, 0x8a, 0x6b, 0xc8, 0x52, 0xc5, 0x46, 0x35, 0x64, - 0x6d, 0xbb, 0xbe, 0x47, 0xc1, 0x3a, 0x19, 0x4b, 0xf3, 0x37, 0x0d, 0x2e, 0x14, 0x53, 0x42, 0x39, - 0xbe, 0x05, 0xd5, 0x24, 0xb8, 0x38, 0x1b, 0xe3, 0x03, 0x93, 0xdc, 0x83, 0xe2, 0x56, 0x4e, 0x5a, - 0x45, 0x4a, 0x5b, 0x19, 0x2a, 0x4d, 0x39, 0xcd, 0x69, 0xdb, 0x83, 0x57, 0xa5, 0xb4, 0x2f, 0x99, - 0xf0, 0x46, 0x2d, 0x99, 0xd3, 0x1e, 0x80, 0x79, 0x07, 0x5e, 0xcb, 0x38, 0xa1, 0xd0, 0x57, 0x61, - 0x22, 0xde, 0xa5, 0xd2, 0x9a, 0x2f, 0x46, 0x2d, 0xb1, 0x12, 0x61, 0x7e, 0x93, 0x31, 0xe7, 0x23, - 0x8b, 0xbc, 0x57, 0x92, 0xa2, 0x17, 0x39, 0xbd, 0x1f, 0x34, 0xc0, 0xac, 0x7b, 0x92, 0x7f, 0x43, - 0xe5, 0x20, 0x39, 0xb5, 0x72, 0xfd, 0x0a, 0x72, 0x76, 0xa7, 0xb5, 0x41, 0x52, 0xb6, 0xdd, 0x96, - 0xbb, 0x9f, 0x4b, 0x85, 0x5c, 0xd8, 0x11, 0x07, 0x91, 0x47, 0xdd, 0x01, 0xd4, 0xd2, 0xc3, 0x83, - 0xc8, 0x33, 0x9f, 0x55, 0xe0, 0xf5, 0x9c, 0x1d, 0xc5, 0xf0, 0x29, 0x9c, 0xef, 0x30, 0x11, 0x84, - 0xfe, 0x8e, 0x02, 0xd3, 0x59, 0x5c, 0x2a, 0x89, 0x25, 0x08, 0x7d, 0x65, 0xbc, 0x59, 0xd1, 0x35, - 0x67, 0xb6, 0x93, 0x59, 0xc1, 0xcf, 0x60, 0x8e, 0x2e, 0x4d, 0xc2, 0xa3, 0x42, 0xbc, 0x5c, 0xe4, - 0xb9, 0xab, 0x50, 0x19, 0xa2, 0xf3, 0x8d, 0xec, 0x12, 0x6e, 0xc2, 0xac, 0x70, 0x9b, 0xcd, 0x83, - 0x84, 0x67, 0x5c, 0xf2, 0x2c, 0x16, 0x79, 0x1e, 0xc6, 0x98, 0x0c, 0xcb, 0x8c, 0xe8, 0x2d, 0xa0, - 0x05, 0x53, 0x64, 0xad, 0x6e, 0xec, 0x85, 0x13, 0xf7, 0x49, 0x25, 0x81, 0x50, 0x66, 0x48, 0xb9, - 0x21, 0x71, 0x23, 0xd7, 0x57, 0xae, 0xab, 0x54, 0x46, 0xee, 0x2a, 0xe6, 0x7d, 0x6a, 0xd4, 0xa9, - 0x3f, 0x3a, 0x8c, 0x3a, 0x9c, 0x23, 0x10, 0x1d, 0xc3, 0xc5, 0x3e, 0xe9, 0x73, 0x12, 0x9c, 0xf9, - 0x24, 0x4f, 0xf5, 0xf2, 0xef, 0xc6, 0x2f, 0x1a, 0x35, 0xfb, 0x9e, 0x02, 0x8a, 0xe6, 0x26, 0x4c, - 0x93, 0xca, 0xe4, 0x86, 0xf4, 0x0d, 0x27, 0x05, 0x9e, 0xdd, 0x3d, 0x79, 0x1f, 0x2e, 0x4a, 0x59, - 0xb2, 0x50, 0x1c, 0x8f, 0xb7, 0x9b, 0xe2, 0x14, 0xf3, 0x50, 0x3f, 0x69, 0x9b, 0x9e, 0xd1, 0xa4, - 0x2c, 0x35, 0x3a, 0xa1, 0xf2, 0xc2, 0x24, 0x1b, 0x85, 0x5c, 0xff, 0xaf, 0x0a, 0x93, 0x92, 0x0f, - 0xbf, 0xd7, 0x60, 0x36, 0x3b, 0xde, 0x71, 0xb5, 0x68, 0xde, 0xef, 0x75, 0x60, 0x5c, 0x1f, 0x01, - 0xa9, 0x24, 0x9a, 0xd7, 0xbe, 0xfd, 0xeb, 0xdf, 0x9f, 0x2b, 0x97, 0x71, 0x91, 0xde, 0x1e, 0xc9, - 0x0b, 0x25, 0xfb, 0x58, 0x88, 0xa5, 0x4c, 0x27, 0x63, 0x05, 0x97, 0x4a, 0xc9, 0x0b, 0xef, 0x08, - 0xe3, 0xad, 0x21, 0x28, 0x72, 0x6f, 0x4b, 0xf7, 0xd7, 0x71, 0xc5, 0x2e, 0xbc, 0x90, 0xd2, 0xd9, - 0x65, 0x1f, 0x66, 0xf2, 0xdf, 0xc5, 0x2e, 0x54, 0xd3, 0xb1, 0x88, 0x83, 0x9d, 0x24, 0xf5, 0x6d, - 0x2c, 0x0f, 0x83, 0x91, 0x98, 0xab, 0x52, 0xcc, 0x22, 0x2e, 0xf4, 0x15, 0x83, 0x3f, 0x6a, 0x30, - 0x11, 0xb7, 0x6a, 0xbc, 0x52, 0xca, 0x99, 0x19, 0x8b, 0xc6, 0xd5, 0x01, 0x08, 0x72, 0x78, 0x47, - 0x3a, 0xbc, 0x8d, 0x1b, 0x23, 0x46, 0x6f, 0xcb, 0xf9, 0x60, 0x1f, 0xca, 0x31, 0xd9, 0xc5, 0xef, - 0x34, 0x98, 0x94, 0x53, 0x06, 0xfb, 0xfb, 0x4a, 0x93, 0x60, 0x0e, 0x82, 0x90, 0x9e, 0x0d, 0xa9, - 0xc7, 0xc6, 0xb5, 0x53, 0xe9, 0xc1, 0x27, 0x30, 0x45, 0xcd, 0xb4, 0xdc, 0x49, 0x6e, 0xfc, 0x18, - 0xd7, 0x06, 0x62, 0x48, 0xc9, 0xdb, 0x52, 0xc9, 0x32, 0x2e, 0x9d, 0x50, 0x22, 0x71, 0xf6, 0x61, - 0x66, 0x82, 0x75, 0xf1, 0x99, 0x06, 0xe7, 0xa8, 0x3d, 0x60, 0x39, 0x7d, 0xbe, 0x5b, 0x1b, 0x4b, - 0x83, 0x41, 0x24, 0xe2, 0xae, 0x14, 0xf1, 0x21, 0x7e, 0x30, 0x6a, 0x3a, 0x92, 0xce, 0x64, 0x1f, - 0xa6, 0xfd, 0xbb, 0x8b, 0x3f, 0x69, 0x30, 0x9d, 0xf4, 0x3b, 0x1c, 0xe8, 0x98, 0x0f, 0xbe, 0x3c, - 0xc5, 0xa6, 0x69, 0xbe, 0x27, 0xf5, 0xad, 0xe3, 0x3b, 0xa7, 0xd5, 0x87, 0xbf, 0x6a, 0x30, 0x93, - 0x69, 0x3e, 0xb8, 0x52, 0xea, 0xf0, 0x64, 0x3b, 0x34, 0x56, 0x87, 0x03, 0x5f, 0xb4, 0x96, 0x64, - 0xff, 0xdb, 0xdc, 0xfa, 0xe3, 0xa8, 0xa6, 0x3d, 0x3f, 0xaa, 0x69, 0xff, 0x1c, 0xd5, 0xb4, 0xa7, - 0xc7, 0xb5, 0xb1, 0xe7, 0xc7, 0xb5, 0xb1, 0xbf, 0x8f, 0x6b, 0x63, 0x5f, 0xad, 0xf9, 0x81, 0x78, - 0xd4, 0xde, 0xb5, 0xf6, 0xd8, 0x7e, 0x42, 0xb9, 0xf6, 0xa8, 0xbd, 0x9b, 0xd2, 0x7f, 0x2d, 0x1d, - 0xc4, 0x05, 0xc1, 0xe3, 0xdf, 0xb4, 0x29, 0xf9, 0x13, 0x75, 0xf3, 0xff, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x3d, 0x6b, 0xab, 0x63, 0xf1, 0x0d, 0x00, 0x00, + // 1030 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5d, 0x6f, 0xdc, 0x44, + 0x14, 0x8d, 0x37, 0x1f, 0xcd, 0xde, 0xa4, 0x01, 0x2e, 0xa1, 0x75, 0xdc, 0xb0, 0xb4, 0x26, 0x24, + 0x69, 0x45, 0x6c, 0x36, 0x25, 0x2d, 0x42, 0x14, 0x44, 0x28, 0x0d, 0x7d, 0x40, 0x0a, 0x6e, 0xc5, + 0x03, 0x2f, 0x91, 0x93, 0xb5, 0x5c, 0x4b, 0x1b, 0x8f, 0xbb, 0x33, 0xbb, 0x22, 0x0a, 0xab, 0x4a, + 0x48, 0x48, 0x94, 0xa7, 0x22, 0x84, 0x10, 0xfd, 0x1d, 0xfc, 0x08, 0x1e, 0x2b, 0x78, 0xe1, 0x11, + 0x25, 0xfc, 0x0e, 0x84, 0x3c, 0x73, 0xed, 0xb5, 0x1d, 0xef, 0x57, 0x55, 0xf1, 0xb8, 0x33, 0xe7, + 0x9e, 0x73, 0xe6, 0xce, 0x9d, 0x7b, 0xbd, 0x60, 0xb8, 0x82, 0x1d, 0xb2, 0xd0, 0xb3, 0x7d, 0xd6, + 0xb1, 0x3b, 0x75, 0xfb, 0x61, 0xdb, 0x6b, 0x1d, 0x59, 0x51, 0x8b, 0x09, 0x86, 0x0b, 0xb4, 0x67, + 0xf9, 0xac, 0x63, 0x75, 0xea, 0xc6, 0xb5, 0x03, 0xc6, 0x0f, 0x19, 0xb7, 0xf7, 0x5d, 0xee, 0x29, + 0xa0, 0xdd, 0xa9, 0xef, 0x7b, 0xc2, 0xad, 0xdb, 0x91, 0xeb, 0x07, 0xa1, 0x2b, 0x02, 0x16, 0xaa, + 0x58, 0x63, 0xd9, 0x67, 0xcc, 0x6f, 0x7a, 0xb6, 0x1b, 0x05, 0xb6, 0x1b, 0x86, 0x4c, 0xc8, 0x4d, + 0x4e, 0xbb, 0x7a, 0x41, 0x35, 0x16, 0x50, 0x3b, 0x4b, 0x4a, 0x63, 0x4f, 0xfe, 0xb2, 0xd5, 0x0f, + 0xb5, 0x65, 0x1a, 0xa0, 0x7f, 0x11, 0x8b, 0x7e, 0xc2, 0x42, 0x2e, 0x02, 0xd1, 0x8e, 0x09, 0x1d, + 0xef, 0x61, 0xdb, 0xe3, 0xc2, 0xfc, 0x08, 0x96, 0x4a, 0xf6, 0x78, 0xc4, 0x42, 0xee, 0xa1, 0x09, + 0xf3, 0x07, 0x99, 0x75, 0x5d, 0xbb, 0xac, 0xad, 0x57, 0x9d, 0xdc, 0x9a, 0x79, 0x13, 0x16, 0x25, + 0xc1, 0x6e, 0x8b, 0x45, 0x8c, 0xbb, 0x4d, 0x22, 0xc6, 0x37, 0x60, 0x2e, 0xa2, 0xa5, 0xbd, 0xa0, + 0x21, 0x43, 0xa7, 0x1c, 0x48, 0x96, 0xee, 0x36, 0xcc, 0xcf, 0xe1, 0xb5, 0x42, 0x20, 0xa9, 0xbe, + 0x0b, 0xb3, 0x09, 0x4c, 0x86, 0xcd, 0x6d, 0xea, 0x56, 0x3e, 0xa1, 0x56, 0x1a, 0x93, 0x22, 0xcd, + 0x27, 0x95, 0x02, 0x1f, 0x4f, 0x9c, 0xec, 0xc0, 0x4b, 0xa9, 0x13, 0x2e, 0x5c, 0xd1, 0xe6, 0x92, + 0x76, 0x61, 0xb3, 0xd6, 0x8f, 0xf6, 0x9e, 0x44, 0x39, 0x0b, 0x51, 0xee, 0x37, 0x5a, 0x30, 0xdd, + 0x61, 0xc2, 0x6b, 0xe9, 0x95, 0x38, 0x0f, 0xdb, 0xfa, 0x1f, 0xbf, 0x6d, 0x2c, 0x52, 0xa2, 0x3f, + 0x6e, 0x34, 0x5a, 0x1e, 0xe7, 0xf7, 0x44, 0x2b, 0x08, 0x7d, 0x47, 0xc1, 0xf0, 0x06, 0x54, 0x1b, + 0x5e, 0xc4, 0x78, 0x20, 0x58, 0x4b, 0x9f, 0x1c, 0x12, 0xd3, 0x83, 0xe2, 0x1d, 0x80, 0x5e, 0x59, + 0xe8, 0x53, 0x32, 0x05, 0xab, 0x16, 0x45, 0xc5, 0x35, 0x64, 0xa9, 0x62, 0xa3, 0x1a, 0xb2, 0x76, + 0x5d, 0xdf, 0xa3, 0xc3, 0x3a, 0x99, 0x48, 0xf3, 0x57, 0x0d, 0x2e, 0x14, 0x53, 0x42, 0x39, 0xbe, + 0x01, 0xd5, 0xe4, 0x70, 0x71, 0x36, 0x26, 0x07, 0x26, 0xb9, 0x07, 0xc5, 0x9d, 0x9c, 0xb5, 0x8a, + 0xb4, 0xb6, 0x36, 0xd4, 0x9a, 0x12, 0xcd, 0x79, 0x3b, 0x80, 0x97, 0xa5, 0xb5, 0x2f, 0x99, 0xf0, + 0x46, 0x2d, 0x99, 0x71, 0x2f, 0xc0, 0xbc, 0x05, 0xaf, 0x64, 0x44, 0xe8, 0xe8, 0xeb, 0x30, 0x15, + 0xef, 0x52, 0x69, 0x2d, 0x16, 0x4f, 0x2d, 0xb1, 0x12, 0x61, 0x7e, 0x93, 0x09, 0xe7, 0x23, 0x9b, + 0xbc, 0x53, 0x92, 0xa2, 0xe7, 0xb9, 0xbd, 0xc7, 0x1a, 0x60, 0x56, 0x9e, 0xec, 0x5f, 0x53, 0x39, + 0x48, 0x6e, 0xad, 0xdc, 0xbf, 0x82, 0xbc, 0xb8, 0xdb, 0xda, 0x22, 0x2b, 0xbb, 0x6e, 0xcb, 0x3d, + 0xcc, 0xa5, 0x42, 0x2e, 0xec, 0x89, 0xa3, 0xc8, 0xa3, 0xee, 0x00, 0x6a, 0xe9, 0xfe, 0x51, 0xe4, + 0x99, 0x4f, 0x2b, 0xf0, 0x6a, 0x2e, 0x8e, 0xce, 0xf0, 0x29, 0x9c, 0xef, 0x30, 0x11, 0x84, 0xfe, + 0x9e, 0x02, 0xd3, 0x5d, 0x2c, 0x97, 0x9c, 0x25, 0x08, 0x7d, 0x15, 0xbc, 0x5d, 0xd1, 0x35, 0x67, + 0xbe, 0x93, 0x59, 0xc1, 0xcf, 0x60, 0x81, 0x1e, 0x4d, 0xc2, 0xa3, 0x8e, 0xf8, 0x7a, 0x91, 0xe7, + 0xb6, 0x42, 0x65, 0x88, 0xce, 0x37, 0xb2, 0x4b, 0xb8, 0x0d, 0xf3, 0xc2, 0x6d, 0x36, 0x8f, 0x12, + 0x9e, 0x49, 0xc9, 0x73, 0xa9, 0xc8, 0x73, 0x3f, 0xc6, 0x64, 0x58, 0xe6, 0x44, 0x6f, 0x01, 0x2d, + 0x98, 0xa1, 0x68, 0xf5, 0x62, 0x2f, 0x9c, 0x79, 0x4f, 0x2a, 0x09, 0x84, 0x32, 0x43, 0xca, 0x0d, + 0x99, 0x1b, 0xb9, 0xbe, 0x72, 0x5d, 0xa5, 0x32, 0x72, 0x57, 0x31, 0xef, 0x52, 0xa3, 0x4e, 0xf5, + 0xe8, 0x32, 0xea, 0x70, 0x8e, 0x40, 0x74, 0x0d, 0x17, 0xfb, 0xa4, 0xcf, 0x49, 0x70, 0xe6, 0xa3, + 0x3c, 0xd5, 0xff, 0xff, 0x36, 0x7e, 0xd6, 0xa8, 0xd9, 0xf7, 0x1c, 0xd0, 0x69, 0xae, 0xc3, 0x2c, + 0xb9, 0x4c, 0x5e, 0x48, 0xdf, 0xe3, 0xa4, 0xc0, 0x17, 0xf7, 0x4e, 0xde, 0x87, 0x8b, 0xd2, 0x96, + 0x2c, 0x14, 0xc7, 0xe3, 0xed, 0xa6, 0x18, 0x63, 0x1e, 0xea, 0x67, 0x63, 0xd3, 0x3b, 0x9a, 0x96, + 0xa5, 0x46, 0x37, 0x54, 0x5e, 0x98, 0x14, 0xa3, 0x90, 0x9b, 0xff, 0x56, 0x61, 0x5a, 0xf2, 0xe1, + 0x63, 0x0d, 0xe6, 0xb3, 0xe3, 0x1d, 0xd7, 0x8b, 0xe1, 0xfd, 0xbe, 0x0e, 0x8c, 0xab, 0x23, 0x20, + 0x95, 0x45, 0x73, 0xe5, 0xdb, 0x3f, 0xff, 0xf9, 0xa9, 0x52, 0xc3, 0x65, 0xbb, 0xf0, 0x89, 0x92, + 0xfd, 0x5a, 0xc0, 0xef, 0x35, 0x98, 0x4d, 0xe6, 0x0a, 0xae, 0x94, 0xb2, 0x17, 0x3e, 0x24, 0x8c, + 0xb7, 0x86, 0xa0, 0x48, 0xdf, 0x96, 0xfa, 0x57, 0x71, 0xad, 0xa8, 0x9f, 0x0e, 0x2f, 0xfb, 0x38, + 0x73, 0x01, 0x5d, 0xec, 0x42, 0x35, 0x9d, 0x8b, 0x38, 0x58, 0x24, 0x29, 0x70, 0x63, 0x75, 0x18, + 0x8c, 0xcc, 0x5c, 0x91, 0x66, 0x2e, 0xe1, 0x52, 0x5f, 0x33, 0xf8, 0x83, 0x06, 0x53, 0x71, 0xaf, + 0xc6, 0xcb, 0xa5, 0x9c, 0x99, 0xb9, 0x68, 0x5c, 0x19, 0x80, 0x20, 0xc1, 0x5b, 0x52, 0xf0, 0x26, + 0x6e, 0x8d, 0x78, 0x7a, 0x5b, 0x0e, 0x08, 0xfb, 0x58, 0xce, 0xc9, 0x2e, 0x7e, 0xa7, 0xc1, 0xb4, + 0x1c, 0x33, 0xd8, 0x5f, 0x2b, 0x4d, 0x82, 0x39, 0x08, 0x42, 0x7e, 0xb6, 0xa4, 0x1f, 0x1b, 0x37, + 0xc6, 0xf2, 0x83, 0x8f, 0x60, 0x86, 0xba, 0x69, 0xb9, 0x48, 0x6e, 0xfe, 0x18, 0x6f, 0x0e, 0xc4, + 0x90, 0x93, 0xb7, 0xa5, 0x93, 0x55, 0x5c, 0x39, 0xe3, 0x44, 0xe2, 0xec, 0xe3, 0xcc, 0x08, 0xeb, + 0xe2, 0x53, 0x0d, 0xce, 0x51, 0x7f, 0xc0, 0x72, 0xfa, 0x7c, 0xbb, 0x36, 0x56, 0x06, 0x83, 0xc8, + 0xc4, 0x6d, 0x69, 0xe2, 0x43, 0xfc, 0x60, 0xd4, 0x74, 0x24, 0xad, 0xc9, 0x3e, 0x4e, 0x1b, 0x78, + 0x17, 0x7f, 0xd4, 0x60, 0x36, 0x69, 0x78, 0x38, 0x50, 0x98, 0x0f, 0x7e, 0x3c, 0xc5, 0xae, 0x69, + 0xbe, 0x27, 0xfd, 0x6d, 0xe2, 0x3b, 0xe3, 0xfa, 0xc3, 0x5f, 0x34, 0x98, 0xcb, 0x74, 0x1f, 0x5c, + 0x2b, 0x15, 0x3c, 0xdb, 0x0f, 0x8d, 0xf5, 0xe1, 0xc0, 0xe7, 0xad, 0x25, 0xd9, 0x00, 0xb7, 0x77, + 0x7e, 0x3f, 0xa9, 0x69, 0xcf, 0x4e, 0x6a, 0xda, 0xdf, 0x27, 0x35, 0xed, 0xc9, 0x69, 0x6d, 0xe2, + 0xd9, 0x69, 0x6d, 0xe2, 0xaf, 0xd3, 0xda, 0xc4, 0x57, 0x1b, 0x7e, 0x20, 0x1e, 0xb4, 0xf7, 0xad, + 0x03, 0x76, 0x98, 0x50, 0x6e, 0x3c, 0x68, 0xef, 0xa7, 0xf4, 0x5f, 0x4b, 0x81, 0xb8, 0x20, 0x78, + 0xfc, 0x3f, 0x6d, 0x46, 0xfe, 0x8b, 0xba, 0xfe, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x17, 0xab, + 0xa6, 0x19, 0xf2, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/gov/types/v1/query.pb.gw.go b/x/gov/types/v1/query.pb.gw.go index 58b486404..c0047cfbf 100644 --- a/x/gov/types/v1/query.pb.gw.go +++ b/x/gov/types/v1/query.pb.gw.go @@ -983,7 +983,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } var ( - pattern_Query_Constitution_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"cosmos", "gov", "v1", "constitution"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_Constitution_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"atomone", "gov", "v1", "constitution"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_Proposal_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"atomone", "gov", "v1", "proposals", "proposal_id"}, "", runtime.AssumeColonVerbOpt(false))) From daa31a0e94c52c94d2bf32c1f3db09677f2e062e Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:31:26 +0200 Subject: [PATCH 06/11] move cli code for unified_diff --- x/gov/client/cli/query.go | 1 - x/gov/client/cli/tx.go | 19 +++-- x/gov/client/cli/tx_test.go | 2 +- x/gov/client/utils/unified_diff.go | 35 ++++++++++ x/gov/client/utils/unified_diff_test.go | 93 +++++++++++++++++++++++++ x/gov/types/unified_diff.go | 30 -------- x/gov/types/unified_diff_test.go | 84 ---------------------- 7 files changed, 144 insertions(+), 120 deletions(-) create mode 100644 x/gov/client/utils/unified_diff.go create mode 100644 x/gov/client/utils/unified_diff_test.go diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index 45a8906db..95e3de6ec 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -40,7 +40,6 @@ func GetQueryCmd() *cobra.Command { GetCmdQueryDeposits(), GetCmdQueryTally(), GetCmdConstitution(), - GetCmdGenerateConstitutionAmendment(), ) return govQueryCmd diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index b790b8e5e..726d7a31d 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/version" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/atomone-hub/atomone/x/gov/client/utils" govutils "github.com/atomone-hub/atomone/x/gov/client/utils" "github.com/atomone-hub/atomone/x/gov/types" v1 "github.com/atomone-hub/atomone/x/gov/types/v1" @@ -73,6 +74,7 @@ func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command { NewCmdWeightedVote(), NewCmdSubmitProposal(), NewCmdDraftProposal(), + NewCmdGenerateConstitutionAmendment(), // Deprecated cmdSubmitLegacyProp, @@ -377,11 +379,11 @@ $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.1 --from mykey return cmd } -// GetCmdConstitutionAmendmentMsg returns the command to generate the sdk.Msg +// 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 GetCmdGenerateConstitutionAmendment() *cobra.Command { +func NewCmdGenerateConstitutionAmendment() *cobra.Command { cmd := &cobra.Command{ Use: "generate-constitution-amendment [path/to/updated/constitution.md]", Args: cobra.ExactArgs(1), @@ -394,6 +396,10 @@ 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 +to submit on-chain. Use the 'tx gov submit-proposal' command in conjunction with the +result of this one to submit the proposal. + Example: $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md `, @@ -419,7 +425,7 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md } // Generate the unified diff between the current and updated constitutions - diff, err := types.GenerateUnifiedDiff(resp.Constitution, updatedConstitution) + diff, err := utils.GenerateUnifiedDiff(resp.Constitution, updatedConstitution) if err != nil { return err } @@ -430,7 +436,12 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md }, } - flags.AddTxFlagsToCmd(cmd) + // 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().Set(flags.FlagOutput, "json") return cmd } diff --git a/x/gov/client/cli/tx_test.go b/x/gov/client/cli/tx_test.go index 6d3be639f..ed8a2c1ff 100644 --- a/x/gov/client/cli/tx_test.go +++ b/x/gov/client/cli/tx_test.go @@ -518,7 +518,7 @@ func (s *CLITestSuite) TestCmdGenerateConstitutionAmendment() { tc := tc s.Run(tc.name, func() { - cmd := cli.GetCmdGenerateConstitutionAmendment() + cmd := cli.NewCmdGenerateConstitutionAmendment() out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) s.Require().NoError(err) s.Require().Contains(out.String(), tc.expCmdOutput) diff --git a/x/gov/client/utils/unified_diff.go b/x/gov/client/utils/unified_diff.go new file mode 100644 index 000000000..80af12a63 --- /dev/null +++ b/x/gov/client/utils/unified_diff.go @@ -0,0 +1,35 @@ +package utils + +import ( + "fmt" + + "github.com/hexops/gotextdiff" + "github.com/hexops/gotextdiff/myers" + "github.com/hexops/gotextdiff/span" +) + +// GenerateUnifiedDiff generates a unified diff from src and dst strings using gotextdiff. +// This is the only function that uses the gotextdiff library as its primary use is for +// clients. +func GenerateUnifiedDiff(src, dst string) (string, error) { + // Create spans for the source and destination texts + srcURI := span.URIFromPath("src") + + if src == "" || src[len(src)-1] != '\n' { + src += "\n" // Add an EOL to src if it's empty or newline is missing + } + if dst == "" || dst[len(dst)-1] != '\n' { + dst += "\n" // Add an EOL to dst if it's empty or newline is missing + } + + // Compute the edits using the Myers diff algorithm + eds := myers.ComputeEdits(srcURI, src, dst) + + // Generate the unified diff string + diff := gotextdiff.ToUnified("src", "dst", src, eds) + + // Convert the diff to a string + diffStr := fmt.Sprintf("%v", diff) + + return diffStr, nil +} diff --git a/x/gov/client/utils/unified_diff_test.go b/x/gov/client/utils/unified_diff_test.go new file mode 100644 index 000000000..3a96d724d --- /dev/null +++ b/x/gov/client/utils/unified_diff_test.go @@ -0,0 +1,93 @@ +package utils_test + +import ( + "strings" + "testing" + + "github.com/atomone-hub/atomone/x/gov/client/utils" + "github.com/atomone-hub/atomone/x/gov/types" + "github.com/stretchr/testify/require" +) + +func TestGenerateUnifiedDiff(t *testing.T) { + tests := []struct { + name string + src string + dst string + expected string + }{ + { + name: "No changes", + src: "Line one\nLine two\nLine three", + dst: "Line one\nLine two\nLine three", + expected: ``, + }, + { + name: "Line added", + src: "Line one\nLine two", + dst: "Line one\nLine two\nLine three", + expected: `@@ -1,2 +1,3 @@ + Line one + Line two ++Line three +`, + }, + { + name: "Line deleted", + src: "Line one\nLine two\nLine three", + dst: "Line one\nLine three", + expected: `@@ -1,3 +1,2 @@ + Line one +-Line two + Line three +`, + }, + { + name: "Line modified", + src: "Line one\nLine two\nLine three", + dst: "Line one\nLine two modified\nLine three", + expected: `@@ -1,3 +1,3 @@ + Line one +-Line two ++Line two modified + Line three +`, + }, + { + name: "Multiple changes", + src: "Line one\nLine two\nLine three", + dst: "Line zero\nLine one\nLine three\nLine four", + expected: `@@ -1,3 +1,4 @@ ++Line zero + Line one +-Line two + Line three ++Line four +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + diff, err := utils.GenerateUnifiedDiff(tt.src, tt.dst) + require.NoError(t, err) + + diffContent := strings.TrimPrefix(diff, "--- src\n+++ dst\n") + expectedContent := strings.TrimPrefix(tt.expected, "--- src\n+++ dst\n") + + require.Equal(t, expectedContent, diffContent) + }) + } +} + +func TestUnifiedDiffIntegration(t *testing.T) { + src := "Line one\nLine two\nLine three" + dst := "Line zero\nLine one\nLine three\nLine four" + + diffStr, err := utils.GenerateUnifiedDiff(src, dst) + require.NoError(t, err) + + result, err := types.ApplyUnifiedDiff(src, diffStr) + require.NoError(t, err) + require.Equal(t, dst, result) +} diff --git a/x/gov/types/unified_diff.go b/x/gov/types/unified_diff.go index 78a2be16e..c7dd0858b 100644 --- a/x/gov/types/unified_diff.go +++ b/x/gov/types/unified_diff.go @@ -4,10 +4,6 @@ import ( "fmt" "strconv" "strings" - - "github.com/hexops/gotextdiff" - "github.com/hexops/gotextdiff/myers" - "github.com/hexops/gotextdiff/span" ) // Hunk represents a single change hunk in a unified diff. @@ -271,29 +267,3 @@ func ApplyUnifiedDiff(src, diffStr string) (string, error) { return strings.Join(resultLines, "\n"), nil } - -// GenerateUnifiedDiff generates a unified diff from src and dst strings using gotextdiff. -// This is the only function that uses the gotextdiff library as its primary use is for -// clients. -func GenerateUnifiedDiff(src, dst string) (string, error) { - // Create spans for the source and destination texts - srcURI := span.URIFromPath("src") - - if src == "" || src[len(src)-1] != '\n' { - src += "\n" // Add an EOL to src if it's empty or newline is missing - } - if dst == "" || dst[len(dst)-1] != '\n' { - dst += "\n" // Add an EOL to dst if it's empty or newline is missing - } - - // Compute the edits using the Myers diff algorithm - eds := myers.ComputeEdits(srcURI, src, dst) - - // Generate the unified diff string - diff := gotextdiff.ToUnified("src", "dst", src, eds) - - // Convert the diff to a string - diffStr := fmt.Sprintf("%v", diff) - - return diffStr, nil -} diff --git a/x/gov/types/unified_diff_test.go b/x/gov/types/unified_diff_test.go index 349e2872d..a486b0295 100644 --- a/x/gov/types/unified_diff_test.go +++ b/x/gov/types/unified_diff_test.go @@ -1,83 +1,11 @@ package types import ( - "strings" "testing" "github.com/stretchr/testify/require" ) -func TestGenerateUnifiedDiff(t *testing.T) { - tests := []struct { - name string - src string - dst string - expected string - }{ - { - name: "No changes", - src: "Line one\nLine two\nLine three", - dst: "Line one\nLine two\nLine three", - expected: ``, - }, - { - name: "Line added", - src: "Line one\nLine two", - dst: "Line one\nLine two\nLine three", - expected: `@@ -1,2 +1,3 @@ - Line one - Line two -+Line three -`, - }, - { - name: "Line deleted", - src: "Line one\nLine two\nLine three", - dst: "Line one\nLine three", - expected: `@@ -1,3 +1,2 @@ - Line one --Line two - Line three -`, - }, - { - name: "Line modified", - src: "Line one\nLine two\nLine three", - dst: "Line one\nLine two modified\nLine three", - expected: `@@ -1,3 +1,3 @@ - Line one --Line two -+Line two modified - Line three -`, - }, - { - name: "Multiple changes", - src: "Line one\nLine two\nLine three", - dst: "Line zero\nLine one\nLine three\nLine four", - expected: `@@ -1,3 +1,4 @@ -+Line zero - Line one --Line two - Line three -+Line four -`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - diff, err := GenerateUnifiedDiff(tt.src, tt.dst) - require.NoError(t, err) - - diffContent := strings.TrimPrefix(diff, "--- src\n+++ dst\n") - expectedContent := strings.TrimPrefix(tt.expected, "--- src\n+++ dst\n") - - require.Equal(t, expectedContent, diffContent) - }) - } -} - func TestApplyUnifiedDiff(t *testing.T) { tests := []struct { name string @@ -221,18 +149,6 @@ func TestApplyUnifiedDiff(t *testing.T) { } } -func TestUnifiedDiffIntegration(t *testing.T) { - src := "Line one\nLine two\nLine three" - dst := "Line zero\nLine one\nLine three\nLine four" - - diffStr, err := GenerateUnifiedDiff(src, dst) - require.NoError(t, err) - - result, err := ApplyUnifiedDiff(src, diffStr) - require.NoError(t, err) - require.Equal(t, dst, result) -} - func TestParseUnifiedDiff(t *testing.T) { diffStr := `@@ -1,3 +1,4 @@ +Line zero From 3824716c990734949abe745053d6ff1adcd46f86 Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:53:29 +0200 Subject: [PATCH 07/11] add cli test form amendments in e2e --- tests/e2e/e2e_gov_test.go | 48 +++++++++++++++++++++++++++++++++---- tests/e2e/e2e_setup_test.go | 1 + x/gov/client/cli/tx.go | 9 ++++--- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/tests/e2e/e2e_gov_test.go b/tests/e2e/e2e_gov_test.go index b2a7e9c6e..83a4d188d 100644 --- a/tests/e2e/e2e_gov_test.go +++ b/tests/e2e/e2e_gov_test.go @@ -1,6 +1,7 @@ package e2e import ( + "context" "fmt" "path/filepath" "strconv" @@ -13,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" ) @@ -183,11 +185,10 @@ func (s *IntegrationTestSuite) testGovConstitutionAmendment() { senderAddress, _ := s.chainA.validators[0].keyInfo.GetAddress() sender := senderAddress.String() - res := s.queryConstitution(chainAAPIEndpoint) newConstitution := "New test constitution" - amendment, err := govtypes.GenerateUnifiedDiff(res.Constitution, newConstitution) - s.Require().NoError(err) - s.writeGovConstitutionAmendmentProposal(s.chainA, amendment) + 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)} @@ -338,3 +339,42 @@ func (s *IntegrationTestSuite) writeGovConstitutionAmendmentProposal(c *chain, a 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 + } +} diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 1fc233c37..db34c1d5c 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -66,6 +66,7 @@ const ( proposalCommunitySpendFilename = "proposal_community_spend.json" proposalParamChangeFilename = "param_change.json" proposalConstitutionAmendmentFilename = "constitution_amendment.json" + newConstitutionFilename = "new_constitution.md" // hermesBinary = "hermes" // hermesConfigWithGasPrices = "/root/.hermes/config.toml" diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 726d7a31d..9e9990dbf 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -14,7 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/version" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/atomone-hub/atomone/x/gov/client/utils" govutils "github.com/atomone-hub/atomone/x/gov/client/utils" "github.com/atomone-hub/atomone/x/gov/types" v1 "github.com/atomone-hub/atomone/x/gov/types/v1" @@ -425,7 +424,7 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md } // Generate the unified diff between the current and updated constitutions - diff, err := utils.GenerateUnifiedDiff(resp.Constitution, updatedConstitution) + diff, err := govutils.GenerateUnifiedDiff(resp.Constitution, updatedConstitution) if err != nil { return err } @@ -441,7 +440,11 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md flags.AddQueryFlagsToCmd(cmd) // query commands have the FlagOutput default to "text", but we want to override it to "json" // in this case. - cmd.Flags().Set(flags.FlagOutput, "json") + cmd.Flags().Lookup(flags.FlagOutput).DefValue = "json" + err := cmd.Flags().Set(flags.FlagOutput, "json") + if err != nil { + panic(err) + } return cmd } From 68e8ee404f17ff70b46d8a1e3e38ca43d0dc7c18 Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:58:12 +0200 Subject: [PATCH 08/11] add positive case for test --- x/gov/keeper/constitution_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x/gov/keeper/constitution_test.go b/x/gov/keeper/constitution_test.go index a031c5f29..d7b95e569 100644 --- a/x/gov/keeper/constitution_test.go +++ b/x/gov/keeper/constitution_test.go @@ -22,6 +22,13 @@ func TestApplyConstitutionAmendment(t *testing.T) { amendment: "Hi World", expectError: true, }, + { + name: "successful patch application", + initialConstitution: "Hello\nWorld", + amendment: "@@ -1,2 +1,2 @@\n-Hello\n+Hi\n World", + expectError: false, + expectedResult: "Hi\nWorld", + }, } for _, tt := range tests { From 66f3953b3afe9aa004f7d08619e5103c77574fac Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:02:38 +0200 Subject: [PATCH 09/11] add more complex test --- x/gov/keeper/constitution_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x/gov/keeper/constitution_test.go b/x/gov/keeper/constitution_test.go index d7b95e569..191be32c2 100644 --- a/x/gov/keeper/constitution_test.go +++ b/x/gov/keeper/constitution_test.go @@ -29,6 +29,13 @@ func TestApplyConstitutionAmendment(t *testing.T) { expectError: false, expectedResult: "Hi\nWorld", }, + { + name: "successful patch application with multiple hunks", + initialConstitution: "Line one\nLine two\nLine three\nLine four\nLine five\nLine six\nLine seven\nLine eight\nLine nine", + amendment: "--- src\n+++ dst\n@@ -1,2 +1,2 @@\n-Line one\n+Line one modified\n Line two\n@@ -8,2 +8,2 @@\n Line eight\n-Line nine\n+Line nine modified", + expectError: false, + expectedResult: "Line one modified\nLine two\nLine three\nLine four\nLine five\nLine six\nLine seven\nLine eight\nLine nine modified", + }, } for _, tt := range tests { From 5a0e5f15f4318c9d6138e7a2924fde99cbf8a938 Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:51:06 +0200 Subject: [PATCH 10/11] remove unused flags --- x/gov/client/cli/prompt.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 6f1249c9b..e485a4358 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -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" @@ -320,7 +319,6 @@ func NewCmdDraftProposal() *cobra.Command { }, } - flags.AddTxFlagsToCmd(cmd) cmd.Flags().Bool(flagSkipMetadata, false, "skip metadata prompt") return cmd From c7ceddbc9c39ab03ea8ff6df949e5b0084e8aa6b Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:09:58 +0200 Subject: [PATCH 11/11] amendment cli utility can also use input file --- x/gov/client/cli/tx.go | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 9e9990dbf..f1362de35 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -383,6 +383,8 @@ $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.1 --from mykey // 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), @@ -390,14 +392,15 @@ func NewCmdGenerateConstitutionAmendment() *cobra.Command { 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 and generates a -valid constitution amendment proposal message containing the unified diff +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 -to submit on-chain. Use the 'tx gov submit-proposal' command in conjunction with the -result of this one to submit the proposal. +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 @@ -412,19 +415,35 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md return err } - // Query the current constitution clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } - queryClient := v1.NewQueryClient(clientCtx) - resp, err := queryClient.Constitution(cmd.Context(), &v1.QueryConstitutionRequest{}) + + 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(resp.Constitution, updatedConstitution) + diff, err := govutils.GenerateUnifiedDiff(currentConstitution, updatedConstitution) if err != nil { return err } @@ -445,6 +464,9 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md 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 }