Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accurate proposal status #80

Merged
merged 2 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 4 additions & 20 deletions plugins/tokenVoting/components/proposal/header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AlertInline, Button, Tag } from "@aragon/ods";
import { Proposal } from "@/plugins/tokenVoting/utils/types";
import { AlertVariant } from "@aragon/ods";
import { getProposalStatusVariant } from "@/plugins/tokenVoting/utils/proposal-status";
import { Else, ElseIf, If, Then } from "@/components/if";
import { AddressText } from "@/components/text/address";
import { PleaseWaitSpinner } from "@/components/please-wait";
Expand All @@ -11,6 +12,7 @@ const DEFAULT_PROPOSAL_TITLE = "(No proposal title)";
interface ProposalHeaderProps {
proposalNumber: number;
proposal: Proposal;
tokenSupply: bigint;
userVote: number | undefined;
canVote: boolean;
canExecute: boolean;
Expand All @@ -22,6 +24,7 @@ interface ProposalHeaderProps {
const ProposalHeader: React.FC<ProposalHeaderProps> = ({
proposalNumber,
proposal,
tokenSupply,
userVote,
canVote,
canExecute,
Expand All @@ -30,7 +33,7 @@ const ProposalHeader: React.FC<ProposalHeaderProps> = ({
onExecute,
}) => {
const userVoteInfo = getUserVoteVariant(userVote);
const proposalVariant = getProposalStatusVariant(proposal);
const proposalVariant = getProposalStatusVariant(proposal, tokenSupply);
const ended = proposal.parameters.endDate <= Date.now() / 1000;

return (
Expand Down Expand Up @@ -125,25 +128,6 @@ const ProposalHeader: React.FC<ProposalHeaderProps> = ({
);
};

const getProposalStatusVariant = (proposal: Proposal) => {
return {
variant: proposal?.active
? "info"
: proposal?.executed
? "primary"
: proposal?.tally?.no >= proposal?.tally?.yes
? "critical"
: ("success" as AlertVariant),
label: proposal?.active
? "Active"
: proposal?.executed
? "Executed"
: proposal?.tally?.no >= proposal?.tally?.yes
? "Defeated"
: "Executable",
};
};

const getUserVoteVariant = (userVote?: number) => {
switch (userVote) {
case 3:
Expand Down
53 changes: 15 additions & 38 deletions plugins/tokenVoting/components/proposal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Link from "next/link";
import { Proposal } from "@/plugins/tokenVoting/utils/types";
import { useProposal } from "@/plugins/tokenVoting/hooks/useProposal";
import { TagVariant } from "@aragon/ods";
import { getProposalStatusVariant } from "@/plugins/tokenVoting/utils/proposal-status";
import { Card, Tag } from "@aragon/ods";
import * as DOMPurify from 'dompurify';
import * as DOMPurify from "dompurify";
import { PleaseWaitSpinner } from "@/components/please-wait";
import { If } from "@/components/if";

Expand All @@ -13,31 +12,11 @@ const DEFAULT_PROPOSAL_METADATA_SUMMARY =

type ProposalInputs = {
proposalId: bigint;
};

const getProposalVariantStatus = (proposal: Proposal) => {
return {
variant: (proposal?.active
? "info"
: proposal?.executed
? "primary"
: proposal?.tally?.no >= proposal?.tally?.yes
? "critical"
: "success") as TagVariant,
label: proposal?.active
? "Active"
: proposal?.executed
? "Executed"
: proposal?.tally?.no >= proposal?.tally?.yes
? "Defeated"
: "Executable",
};
tokenSupply: bigint;
};

export default function ProposalCard(props: ProposalInputs) {
const { proposal, status } = useProposal(
props.proposalId.toString()
);
const { proposal, status } = useProposal(props.proposalId.toString());

const showLoading = getShowProposalLoading(proposal, status);

Expand All @@ -53,15 +32,11 @@ export default function ProposalCard(props: ProposalInputs) {
);
} else if (status.metadataReady && !proposal?.title) {
return (
<Link
href={`#/proposals/${props.proposalId}`}
className="w-full mb-4"
>
<Link href={`#/proposals/${props.proposalId}`} className="w-full mb-4">
<Card className="p-4">
<div className="md:w-7/12 lg:w-3/4 xl:4/5 pr-4 text-nowrap text-ellipsis overflow-hidden">
<h4 className="mb-1 text-lg text-neutral-300 line-clamp-1">
{Number(props.proposalId) + 1} -{" "}
{DEFAULT_PROPOSAL_METADATA_TITLE}
{Number(props.proposalId) + 1} - {DEFAULT_PROPOSAL_METADATA_TITLE}
</h4>
<p className="text-base text-neutral-300 line-clamp-3">
{DEFAULT_PROPOSAL_METADATA_SUMMARY}
Expand All @@ -72,29 +47,31 @@ export default function ProposalCard(props: ProposalInputs) {
);
}

const { variant: statusVariant, label: statusLabel } =
getProposalStatusVariant(proposal, props.tokenSupply);

return (
<Link href={`#/proposals/${props.proposalId}`} className="w-full">
<Card className="w-full mb-4 p-5">
<div className="w-full">
<If condition={proposal.tally}>
<div className="flex mb-2">
<Tag
variant={getProposalVariantStatus(proposal as Proposal).variant}
label={getProposalVariantStatus(proposal as Proposal).label}
/>
<Tag variant={statusVariant as any} label={statusLabel} />
</div>
</If>

<div className="text-ellipsis overflow-hidden">
<h4 className=" mb-1 text-lg font-semibold text-dark line-clamp-1">
{Number(props.proposalId) + 1} - {proposal.title}
</h4>
<div className="text-ellipsis overflow-hidden box line-clamp-2"
<div
className="text-ellipsis overflow-hidden box line-clamp-2"
dangerouslySetInnerHTML={{
__html: proposal.summary
? DOMPurify.sanitize(proposal.summary)
: DEFAULT_PROPOSAL_METADATA_SUMMARY
}} />
: DEFAULT_PROPOSAL_METADATA_SUMMARY,
}}
/>
</div>
</div>
</Card>
Expand Down
3 changes: 3 additions & 0 deletions plugins/tokenVoting/pages/proposal-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import { If } from "@/components/if";
import { PleaseWaitSpinner } from "@/components/please-wait";
import { useSkipFirstRender } from "@/hooks/useSkipFirstRender";
import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants";
import { useVotingToken } from "@/plugins/tokenVoting/hooks/useVotingToken";

const PROPOSALS_PER_PAGE = 10;

export default function Proposals() {
const [proposalCount, setProposalCount] = useState(0);
const { data: blockNumber } = useBlockNumber({ watch: true });
const canCreate = useCanCreateProposal();
const { tokenSupply } = useVotingToken();

const [currentPage, setCurrentPage] = useState(0);
const [paginatedProposals, setPaginatedProposals] = useState<number[]>([]);
Expand Down Expand Up @@ -72,6 +74,7 @@ export default function Proposals() {
proposalId={BigInt(
proposalCount! - 1 - currentPage * PROPOSALS_PER_PAGE - i
)}
tokenSupply={tokenSupply || BigInt("0")}
/>
))}
<div className="w-full flex flex-row justify-end gap-2 mt-4 mb-10">
Expand Down
3 changes: 3 additions & 0 deletions plugins/tokenVoting/pages/proposal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Else, If, Then } from "@/components/if";
import { PleaseWaitSpinner } from "@/components/please-wait";
import { useSkipFirstRender } from "@/hooks/useSkipFirstRender";
import { useProposalVoting } from "../hooks/useProposalVoting";
import { useVotingToken } from "../hooks/useVotingToken";
import { useProposalExecute } from "../hooks/useProposalExecute";

type BottomSection = "description" | "votes";
Expand All @@ -26,6 +27,7 @@ export default function ProposalDetail({ id: proposalId }: { id: string }) {
proposalId,
true
);
const { tokenSupply } = useVotingToken();
const {
voteProposal,
votingStatus,
Expand Down Expand Up @@ -109,6 +111,7 @@ export default function ProposalDetail({ id: proposalId }: { id: string }) {
<ProposalHeader
proposalNumber={Number(proposalId) + 1}
proposal={proposal}
tokenSupply={tokenSupply || BigInt("0")}
userVote={votedOption}
transactionConfirming={showTransactionConfirming}
canVote={!!userCanVote}
Expand Down
45 changes: 45 additions & 0 deletions plugins/tokenVoting/utils/proposal-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Proposal, VotingMode } from "@/plugins/tokenVoting/utils/types";
export const RATIO_BASE = 1_000_000;

export function getProposalStatusVariant(
proposal: Proposal,
tokenSupply: bigint
) {
// Terminal cases
if (!proposal?.tally) return { variant: "info", label: "(Loading)" };
else if (proposal.executed) return { variant: "primary", label: "Executed" };

const supportThreshold = proposal.parameters.supportThreshold;

if (!proposal.active) {
// Defeated or executable?
const yesNoVotes = proposal.tally.no + proposal.tally.yes;
if (!yesNoVotes) return { variant: "critical", label: "Defeated" };

const totalVotes = proposal.tally.abstain + yesNoVotes;
if (totalVotes < proposal.parameters.minVotingPower) {
return { variant: "critical", label: "Low turnout" };
}

const finalRatio = (BigInt(RATIO_BASE) * proposal.tally.yes) / yesNoVotes;

if (finalRatio > BigInt(supportThreshold)) {
return { variant: "success", label: "Executable" };
}
return { variant: "critical", label: "Defeated" };
}

// Active or early execution?
const noVotesWorstCase =
tokenSupply - proposal.tally.yes - proposal.tally.abstain;
const totalYesNoWc = proposal.tally.yes + noVotesWorstCase;

if (proposal.parameters.votingMode == VotingMode.EarlyExecution) {
const currentRatio =
(BigInt(RATIO_BASE) * proposal.tally.yes) / totalYesNoWc;
if (currentRatio > BigInt(supportThreshold)) {
return { variant: "success", label: "Executable" };
}
}
return { variant: "info", label: "Active" };
}
2 changes: 1 addition & 1 deletion plugins/tokenVoting/utils/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type ProposalInputs = {
proposalId: bigint;
};

enum VotingMode {
export enum VotingMode {
Standard,
EarlyExecution,
VoteReplacement,
Expand Down
Loading