From 15f6786a0f44cfecb02173b789e6f64bbae2bcbb Mon Sep 17 00:00:00 2001 From: GregoryNEUT Date: Fri, 12 Aug 2022 10:34:14 +0400 Subject: [PATCH] Implement set error flag (#121) * Implement set error flag --- components/ProposalActions.tsx | 173 +++++++++++++----- components/ProposalFilter.tsx | 10 - components/ProposalStatusBadge.tsx | 6 - .../ExecuteAllInstructionButton.tsx | 1 - .../instructions/ExecuteInstructionButton.tsx | 3 +- .../FlagInstructionErrorButton.tsx | 2 + components/instructions/instructionCard.tsx | 12 +- components/instructions/instructionPanel.tsx | 8 +- pages/dao/[symbol]/proposal/[pk].tsx | 3 +- .../proposal/components/MyProposalsBtn.tsx | 1 - stores/useWalletStore.tsx | 50 +---- 11 files changed, 146 insertions(+), 123 deletions(-) diff --git a/components/ProposalActions.tsx b/components/ProposalActions.tsx index b64eb87e6d..a8d63f785f 100644 --- a/components/ProposalActions.tsx +++ b/components/ProposalActions.tsx @@ -1,7 +1,10 @@ import { useEffect, useState } from 'react'; import { useHasVoteTimeExpired } from '../hooks/useHasVoteTimeExpired'; import useRealm from '../hooks/useRealm'; -import { getSignatoryRecordAddress } from '@solana/spl-governance'; +import { + getSignatoryRecordAddress, + InstructionExecutionStatus, +} from '@solana/spl-governance'; import useWalletStore, { EnhancedProposalState, } from '../stores/useWalletStore'; @@ -15,11 +18,14 @@ import { Proposal } from '@solana/spl-governance'; import { ProgramAccount } from '@solana/spl-governance'; import { cancelProposal } from 'actions/cancelProposal'; import { getProgramVersionForRealm } from '@models/registry/api'; +import { flagInstructionError } from 'actions/flagInstructionError'; +import useProposal from '@hooks/useProposal'; const ProposalActionsPanel = () => { const { governance, proposal, proposalOwner } = useWalletStore( (s) => s.selectedProposal, ); + const { instructions } = useProposal(); const { realmInfo } = useRealm(); const wallet = useWalletStore((s) => s.current); const connected = useWalletStore((s) => s.connected); @@ -69,6 +75,20 @@ const ProposalActionsPanel = () => { wallet.publicKey, ); + const canSetFlagToExecutionError = + proposal && + governance && + proposalOwner && + wallet?.publicKey && + proposalOwner?.account.governingTokenOwner.equals(wallet.publicKey) && + (proposal?.account.state === EnhancedProposalState.Succeeded || + proposal?.account.state === EnhancedProposalState.Executing || + proposal?.account.state === EnhancedProposalState.ExecutingWithErrors) && + Object.values(instructions).some( + (instruction) => + instruction.account.executionStatus === InstructionExecutionStatus.None, + ); + const signOffTooltipContent = !connected ? 'Connect your wallet to sign off this proposal' : !signatoryRecord @@ -102,6 +122,11 @@ const ProposalActionsPanel = () => { !hasVoteTimeExpired ? 'Proposal is being voting right now, you need to wait the vote to finish to be able to finalize it.' : ''; + + const setFlagToExecutionErrorTooltipContent = !connected + ? 'Connect your wallet to set the error flag for this proposal.' + : 'Set error execution flag for this proposal.'; + const handleFinalizeVote = async () => { try { if (proposal && realmInfo && governance) { @@ -127,6 +152,51 @@ const ProposalActionsPanel = () => { } }; + const handleSetExecutionErrorFlag = async () => { + try { + if (proposal && realmInfo && governance) { + const rpcContext = new RpcContext( + proposal.owner, + getProgramVersionForRealm(realmInfo), + wallet!, + connection.current, + connection.endpoint, + ); + + if (Object.keys(instructions).length === 0) { + notify({ + type: 'info', + message: 'Cannot set the error flag', + description: 'The proposal does not contain any instruction', + }); + + return; + } + + const filteredInstructions = Object.values(instructions).filter( + (instruction) => + instruction.account.executionStatus === + InstructionExecutionStatus.None, + ); + + // Set flag error for one instruction after another until there are no more to set flag to + for (const instruction of filteredInstructions) { + await flagInstructionError(rpcContext, proposal, instruction.pubkey); + } + + await fetchRealm(realmInfo!.programId, realmInfo!.realmId); + } + } catch (error) { + notify({ + type: 'error', + message: `Error: Could not finalize vote.`, + description: `${error}`, + }); + + console.error('error finalizing vote', error); + } + }; + const handleSignOffProposal = async () => { try { if (proposal && realmInfo) { @@ -157,6 +227,7 @@ const ProposalActionsPanel = () => { console.error('error sign off', error); } }; + const handleCancelProposal = async ( proposal: ProgramAccount | undefined, ) => { @@ -184,51 +255,63 @@ const ProposalActionsPanel = () => { console.error('error cancelling proposal', error); } }; + + if ( + !canCancelProposal && + !canSignOff && + !canFinalizeVote && + !canSetFlagToExecutionError + ) { + return null; + } + return ( - <> - {EnhancedProposalState.Cancelled === proposal?.account.state || - EnhancedProposalState.Succeeded === proposal?.account.state || - EnhancedProposalState.Outdated === proposal?.account.state || - EnhancedProposalState.Defeated === proposal?.account.state || - (!canCancelProposal && !canSignOff && !canFinalizeVote) ? null : ( -
-
- {canSignOff && ( - - )} - - {canCancelProposal && ( - handleCancelProposal(proposal)} - disabled={!connected} - > - Cancel - - )} - - {canFinalizeVote && ( - - )} -
-
- )} - +
+
+ {canSignOff && ( + + )} + + {canCancelProposal && ( + handleCancelProposal(proposal)} + disabled={!connected} + > + Cancel + + )} + + {canFinalizeVote && ( + + )} + + {canSetFlagToExecutionError && ( + + )} +
+
); }; diff --git a/components/ProposalFilter.tsx b/components/ProposalFilter.tsx index 405d021b80..f33c85f6d9 100644 --- a/components/ProposalFilter.tsx +++ b/components/ProposalFilter.tsx @@ -19,7 +19,6 @@ const initialFilterSettings: Filters = { [EnhancedProposalState.Cancelled]: false, [EnhancedProposalState.Defeated]: true, [EnhancedProposalState.ExecutingWithErrors]: true, - [EnhancedProposalState.Outdated]: false, }; const StyledAlertCount = styled.span` @@ -159,15 +158,6 @@ const ProposalFilter = ({ filters, setFilters }) => { } /> -
- Outdated - - handleFilters(EnhancedProposalState.Outdated, checked) - } - /> -
Voting s.connection); if ( + proposal.account.state === ProposalState.ExecutingWithErrors || playState !== PlayState.Error || proposalInstruction.account.executionStatus !== InstructionExecutionStatus.Error || diff --git a/components/instructions/instructionCard.tsx b/components/instructions/instructionCard.tsx index 51e17869b0..18ca543f0e 100644 --- a/components/instructions/instructionCard.tsx +++ b/components/instructions/instructionCard.tsx @@ -3,6 +3,7 @@ import { ExternalLinkIcon } from '@heroicons/react/outline'; import { AccountMetaData, Proposal, + ProposalState, ProposalTransaction, } from '@solana/spl-governance'; import { @@ -169,10 +170,12 @@ export default function InstructionCard({ > )} + + /> +
{proposalInstruction.account .getSingleInstruction() @@ -202,7 +205,7 @@ export default function InstructionCard({
) : ( - + )} { @@ -236,7 +239,8 @@ export default function InstructionCard({ proposalInstruction={proposalInstruction} /> - {proposal && ( + {proposal && + proposal.account.state !== ProposalState.ExecutingWithErrors ? ( - )} + ) : null}
); diff --git a/components/instructions/instructionPanel.tsx b/components/instructions/instructionPanel.tsx index bc8b7618fb..e08c9f5994 100644 --- a/components/instructions/instructionPanel.tsx +++ b/components/instructions/instructionPanel.tsx @@ -4,7 +4,7 @@ import { Disclosure } from '@headlessui/react'; import { ChevronDownIcon } from '@heroicons/react/solid'; import { useEffect, useRef, useState } from 'react'; import useWalletStore from 'stores/useWalletStore'; -import { RpcContext } from '@solana/spl-governance'; +import { ProposalState, RpcContext } from '@solana/spl-governance'; import useRealm from '@hooks/useRealm'; import { getProgramVersionForRealm } from '@models/registry/api'; import { @@ -104,7 +104,9 @@ export function InstructionPanel() { ))} - {proposal && proposalInstructions.length > 1 && ( + {proposal && + proposalInstructions.length > 1 && + proposal.account.state !== ProposalState.ExecutingWithErrors ? (
- )} + ) : null} )} diff --git a/pages/dao/[symbol]/proposal/[pk].tsx b/pages/dao/[symbol]/proposal/[pk].tsx index 905b216927..84c1d07308 100644 --- a/pages/dao/[symbol]/proposal/[pk].tsx +++ b/pages/dao/[symbol]/proposal/[pk].tsx @@ -40,8 +40,7 @@ const Proposal = () => { (proposal.account.state === EnhancedProposalState.Completed || proposal.account.state === EnhancedProposalState.Executing || proposal.account.state === EnhancedProposalState.SigningOff || - proposal.account.state === EnhancedProposalState.Succeeded || - proposal.account.state === EnhancedProposalState.Outdated); + proposal.account.state === EnhancedProposalState.Succeeded); useEffect(() => { const handleResolveDescription = async () => { diff --git a/pages/dao/[symbol]/proposal/components/MyProposalsBtn.tsx b/pages/dao/[symbol]/proposal/components/MyProposalsBtn.tsx index 9f65f7a8f5..2f1f2d1c9a 100644 --- a/pages/dao/[symbol]/proposal/components/MyProposalsBtn.tsx +++ b/pages/dao/[symbol]/proposal/components/MyProposalsBtn.tsx @@ -74,7 +74,6 @@ const MyProposalsBn = () => { const unReleased = myProposals.filter( (x) => (x.account.state === EnhancedProposalState.Succeeded || - x.account.state === EnhancedProposalState.Outdated || x.account.state === EnhancedProposalState.Completed) && x.account.isVoteFinalized() && !ownVoteRecordsByProposal[x.pubkey.toBase58()]?.account.isRelinquished, diff --git a/stores/useWalletStore.tsx b/stores/useWalletStore.tsx index abe7615773..64907718f2 100644 --- a/stores/useWalletStore.tsx +++ b/stores/useWalletStore.tsx @@ -48,17 +48,12 @@ import { } from '@models/api'; import { accountsToPubkeyMap } from '@tools/sdk/accounts'; import { HIDDEN_PROPOSALS } from '@components/instructions/tools'; -import { BN } from '@project-serum/anchor'; export const EnhancedProposalState = { ...ProposalState, - - // Special Enhanced Type - // Outdated = Suceeded proposal that did not get executed for a long time - Outdated: 42 as const, } as const; -export type EnhancedProposalState = ProposalState | 42; +export type EnhancedProposalState = ProposalState; export declare type EnhancedProposal = Omit & { state: EnhancedProposalState; @@ -131,43 +126,6 @@ const INITIAL_PROPOSAL_STATE: WalletStore['selectedProposal'] = { loading: true, }; -const TEN_DAYS_IN_MS = 3_600 * 24 * 10 * 1_000; - -function isProposalOutdated( - proposal: ProgramAccount, -): boolean { - if (proposal.account.state !== EnhancedProposalState.Succeeded) { - return false; - } - - if (!proposal.account.votingCompletedAt) { - return false; - } - - if ( - Date.now() - - proposal.account.votingCompletedAt.mul(new BN(1_000)).toNumber() <= - TEN_DAYS_IN_MS - ) { - return false; - } - - return true; -} - -// Set Outdated flag for proposals that succeeded more than 10 days ago -function setOutdatedStateForProposals( - proposals: Record>, -) { - Object.values(proposals).forEach((proposal) => { - if (!isProposalOutdated(proposal)) { - return; - } - - proposal.account.state = EnhancedProposalState.Outdated; - }); -} - const useWalletStore = create((set, get) => ({ connected: false, connection: getConnectionContext('mainnet'), @@ -364,8 +322,6 @@ const useWalletStore = create((set, get) => ({ .filter((p) => !HIDDEN_PROPOSALS.has(p.pubkey.toBase58())), ); - setOutdatedStateForProposals(proposals); - set((s) => { s.selectedRealm.proposals = proposals; s.selectedRealm.loading = false; @@ -407,10 +363,6 @@ const useWalletStore = create((set, get) => ({ Proposal, ); - if (isProposalOutdated(proposal)) { - proposal.account.state = EnhancedProposalState.Outdated; - } - const proposalMint = realmMints[proposal.account.governingTokenMint.toBase58()];