Skip to content

Commit

Permalink
Merge pull request #2341 from IntersectMBO/fix/2183--cip-129-support-…
Browse files Browse the repository at this point in the history
…on-govtool

feat(#2183): add support for CIP-129 governance identifiers
  • Loading branch information
MSzalowski authored Nov 7, 2024
2 parents b47c775 + 61a7ba8 commit 9913448
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 15 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ changes.

### Added

-
- add support for CIP-129 governance identifiers [Issue 2183](https://github.com/IntersectMBO/govtool/issues/2183)

### Fixed

Expand All @@ -21,6 +21,7 @@ changes.
### Changed

- Change multilanguage support to use json file [Issue 2325](https://github.com/IntersectMBO/govtool/issues/2325)
- Display full Governance Action IDs on desktop

### Removed

Expand Down
17 changes: 15 additions & 2 deletions govtool/frontend/src/components/molecules/GovernanceActionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {

import { useScreenDimension, useTranslation } from "@hooks";
import {
encodeCIP129Identifier,
getFullGovActionId,
getProposalTypeLabel,
getProposalTypeNoEmptySpaces,
Expand Down Expand Up @@ -53,6 +54,11 @@ export const GovernanceActionCard: FC<ActionTypeProps> = ({ ...props }) => {
const { t } = useTranslation();

const govActionId = getFullGovActionId(txHash, index);
const cip129GovernanceActionId = encodeCIP129Identifier(
txHash,
index.toString(16).padStart(2, "0"),
"gov_action",
);

return (
<Box
Expand Down Expand Up @@ -113,8 +119,15 @@ export const GovernanceActionCard: FC<ActionTypeProps> = ({ ...props }) => {
/>
<GovernanceActionCardElement
label={t("govActions.governanceActionId")}
text={getFullGovActionId(txHash, index)}
dataTestId={`${getFullGovActionId(txHash, index)}-id`}
text={govActionId}
dataTestId={`${govActionId}-id`}
isCopyButton
isSliderCard
/>
<GovernanceActionCardElement
label={t("govActions.cip129GovernanceActionId")}
text={cip129GovernanceActionId}
dataTestId={`${cip129GovernanceActionId}-id`}
isCopyButton
isSliderCard
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button } from "@atoms";
import { PATHS } from "@consts";
import { useScreenDimension, useTranslation } from "@hooks";
import {
encodeCIP129Identifier,
getFullGovActionId,
getProposalTypeLabel,
getProposalTypeNoEmptySpaces,
Expand Down Expand Up @@ -43,6 +44,13 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => {
const { isMobile, screenWidth } = useScreenDimension();
const { t } = useTranslation();

const govActionId = getFullGovActionId(txHash, index);
const cip129GovernanceActionId = encodeCIP129Identifier(
txHash,
index.toString(16).padStart(2, "0"),
"gov_action",
);

return (
<Box
sx={{
Expand Down Expand Up @@ -101,8 +109,15 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => {
/>
<GovernanceActionCardElement
label={t("govActions.governanceActionId")}
text={getFullGovActionId(txHash, index)}
dataTestId={`${getFullGovActionId(txHash, index)}-id`}
text={govActionId}
dataTestId={`${govActionId}-id`}
isCopyButton
isSliderCard
/>
<GovernanceActionCardElement
label={t("govActions.cip129GovernanceActionId")}
text={cip129GovernanceActionId}
dataTestId={`${cip129GovernanceActionId}-id`}
isCopyButton
isSliderCard
/>
Expand All @@ -120,15 +135,12 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => {
>
<Button
disabled={inProgress}
data-testid={`govaction-${getFullGovActionId(
txHash,
index,
)}-change-your-vote`}
data-testid={`govaction-${govActionId}-change-your-vote`}
onClick={() =>
navigate(
PATHS.dashboardGovernanceActionsAction.replace(
":proposalId",
getFullGovActionId(txHash, index),
govActionId,
),
{
state: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
filterOutNullParams,
getFullGovActionId,
mapArrayToObjectByKeys,
encodeCIP129Identifier,
} from "@utils";
import { MetadataValidationStatus, ProposalData } from "@models";
import { GovernanceActionType } from "@/types/governanceAction";
Expand Down Expand Up @@ -142,6 +143,11 @@ export const GovernanceActionDetailsCardData = ({

const label = getProposalTypeLabel(type);
const govActionId = getFullGovActionId(txHash, index);
const cip129GovernanceActionId = encodeCIP129Identifier(
txHash,
index.toString(16).padStart(2, "0"),
"gov_action",
);
const prevGovActionId =
prevGovActionIndex && prevGovActionTxHash
? getFullGovActionId(prevGovActionTxHash, prevGovActionIndex)
Expand Down Expand Up @@ -243,6 +249,14 @@ export const GovernanceActionDetailsCardData = ({
text={govActionId}
isCopyButton
dataTestId={`${govActionId}-id`}
textVariant={screenWidth > 1600 ? "longText" : "oneLine"}
/>
<GovernanceActionCardElement
label={t("govActions.cip129GovernanceActionId")}
text={cip129GovernanceActionId}
dataTestId={`${cip129GovernanceActionId}-id`}
isCopyButton
textVariant={screenWidth > 1600 ? "longText" : "oneLine"}
/>

{tabs.length === 1 ? (
Expand Down
1 change: 1 addition & 0 deletions govtool/frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@
"sPos": "SPOs",
"ccCommittee": "Constitutional Committee",
"governanceActionId": "Governance Action ID:",
"cip129GovernanceActionId": "(CIP-129) Governance Action ID:",
"governanceActionType": "Governance Action Type:",
"goToVote": "Go to Vote",
"protocolParamsDetails": {
Expand Down
9 changes: 8 additions & 1 deletion govtool/frontend/src/services/requests/getProposal.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { VotedProposal, VotedProposalDTO } from "@/models";
import { decodeCIP129Identifier, mapDtoToProposal } from "@/utils";

import { API } from "../API";
import { mapDtoToProposal } from "@/utils";

export const getProposal = async (
proposalId: string,
drepId?: string,
): Promise<VotedProposal> => {
const isCIP129Identifier = proposalId.includes("gov_action");
if (isCIP129Identifier) {
const { txID } = decodeCIP129Identifier(proposalId);
proposalId = txID;
}

const encodedHash = encodeURIComponent(proposalId);

const { data } = await API.get<VotedProposalDTO>(
Expand Down
4 changes: 3 additions & 1 deletion govtool/frontend/src/services/requests/getProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export const getProposals = async ({
params: {
page,
pageSize,
...(searchPhrase && { search: searchPhrase }),
...(searchPhrase && {
search: searchPhrase,
}),
...(filters.length && { type: filters }),
...(sorting && { sort: sorting }),
...(dRepID && { drepId: dRepID }),
Expand Down
15 changes: 13 additions & 2 deletions govtool/frontend/src/stories/GovernanceAction.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { MetadataValidationStatus } from "@models";
import { expect, jest } from "@storybook/jest";
import type { Meta, StoryObj } from "@storybook/react";
import { screen, userEvent, waitFor, within } from "@storybook/testing-library";
import { formatDisplayDate, getProposalTypeNoEmptySpaces } from "@utils";
import {
encodeCIP129Identifier,
formatDisplayDate,
getProposalTypeNoEmptySpaces,
} from "@utils";
import { GovernanceActionCard } from "@/components/molecules";
import { GovernanceActionType } from "@/types/governanceAction";

Expand Down Expand Up @@ -47,6 +51,12 @@ const commonArgs = {
prevGovActionTxHash: null,
};

const cip129GovActionId = encodeCIP129Identifier(
commonArgs.txHash,
commonArgs.index.toString(16).padStart(2, "0"),
"gov_action",
);

export const GovernanceActionCardComponent: Story = {
args: commonArgs,

Expand All @@ -58,6 +68,7 @@ export const GovernanceActionCardComponent: Story = {
),
).toBeInTheDocument();
expect(canvas.getByTestId("sad78afdsf7jasd98d#2-id")).toBeInTheDocument();
expect(canvas.getByTestId(`${cip129GovActionId}-id`)).toBeInTheDocument();
expect(
canvas.getByText(formatDisplayDate("1970-01-01T00:00:00Z")),
).toBeInTheDocument();
Expand All @@ -79,7 +90,7 @@ export const GovernanceActionCardComponent: Story = {
await userEvent.click(
canvas.getByTestId("govaction-sad78afdsf7jasd98d#2-view-detail"),
);
await expect(args.onClick).toHaveBeenCalled();
await await expect(args.onClick).toHaveBeenCalled();
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { expect, jest } from "@storybook/jest";
import type { Meta, StoryObj } from "@storybook/react";
import { screen, userEvent, waitFor, within } from "@storybook/testing-library";
import {
encodeCIP129Identifier,
formatDisplayDate,
getFullGovActionId,
getProposalTypeNoEmptySpaces,
Expand Down Expand Up @@ -59,6 +60,12 @@ const govActionId = getFullGovActionId(
commonArgs.proposal.index,
);

const cip129GovActionId = encodeCIP129Identifier(
commonArgs.proposal.txHash,
commonArgs.proposal.index.toString(16).padStart(2, "0"),
"gov_action",
);

async function assertTooltip(tooltip: HTMLElement, expectedText: RegExp) {
await userEvent.hover(tooltip);
await waitFor(async () => {
Expand All @@ -82,6 +89,9 @@ async function assertGovActionDetails(
await expect(canvas.getByTestId(`${govActionId}-id`)).toHaveTextContent(
govActionId,
);
await expect(canvas.getByTestId(`${cip129GovActionId}-id`)).toHaveTextContent(
cip129GovActionId,
);
}

export const GovernanceActionDetailsCardComponent: Story = {
Expand Down
32 changes: 32 additions & 0 deletions govtool/frontend/src/utils/cip129identifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { bech32 } from "bech32";
import { Buffer } from "buffer";

/**
* Encodes a CIP129 identifier based on the provided transaction ID, index, and bech32 prefix.
* @param txID - The transaction ID.
* @param index - The index.
* @param bech32Prefix - The bech32 prefix.
* @returns The generated CIP129 identifier.
*/
export const encodeCIP129Identifier = (
txID: string,
index: string,
bech32Prefix: string,
) => {
const govActionBytes = Buffer.from(txID + index, "hex");
const words = bech32.toWords(govActionBytes);
return bech32.encode(bech32Prefix, words);
};

/**
* Decodes a CIP129 identifier.
* @param cip129Identifier - The CIP129 identifier to decode.
* @returns An object containing the decoded transaction ID, index, and prefix.
*/
export const decodeCIP129Identifier = (cip129Identifier: string) => {
const { prefix, words } = bech32.decode(cip129Identifier);
const buffer = Buffer.from(bech32.fromWords(words));
const txID = buffer.subarray(0, 32).toString("hex");
const index = buffer.subarray(32).toString("hex");
return { txID, index, prefix };
};
5 changes: 4 additions & 1 deletion govtool/frontend/src/utils/getGovActionId.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const getShortenedGovActionId = (txHash: string, index: number | string) => {
export const getShortenedGovActionId = (
txHash: string,
index: number | string,
) => {
if (txHash.length <= 6) {
return `${txHash}#${index}`;
}
Expand Down
1 change: 1 addition & 0 deletions govtool/frontend/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export * from "./removeDuplicatedProposals";
export * from "./setProtocolParameterUpdate";
export * from "./testIdFromLabel";
export * from "./wait";
export * from "./cip129identifier";
32 changes: 32 additions & 0 deletions govtool/frontend/src/utils/tests/cip129identifier.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
encodeCIP129Identifier,
decodeCIP129Identifier,
} from "../cip129identifier";

const txID = "0e52f799df70b3b74e57d3c7c89f5d44b6deffb3b8b3a718abf9bb41d33e3a92";
const index = "00";
const bech32Prefix = "gov_action";

describe("CIP-129 Governance Identifier", () => {
describe("encodeCIP129Identifier", () => {
it("should encode a CIP129 identifier correctly", () => {
const result = encodeCIP129Identifier(txID, index, bech32Prefix);
expect(result).toBe(
"gov_action1pef00xwlwzemwnjh60ru386agjmdalanhze6wx9tlxa5r5e782fqqt7spu6",
);
});
});

describe("decodeCIP129Identifier", () => {
it("should decode a CIP129 identifier correctly", () => {
const result = decodeCIP129Identifier(
encodeCIP129Identifier(txID, index, bech32Prefix),
);
expect(result).toEqual({
txID,
index,
prefix: bech32Prefix,
});
});
});
});

0 comments on commit 9913448

Please sign in to comment.