diff --git a/Dockerfile b/Dockerfile index 8683c14..70a93e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ ENV NEXT_TELEMETRY_DISABLED=1 \ CHAIN_ID=$CHAIN_ID WORKDIR /app -RUN apk add --no-cache curl=~8.5.0-r0 +RUN apk add --no-cache curl=~8.9.0-r0 COPY --from=build /app /app USER node diff --git a/features/aragon/useAragon.ts b/features/aragon/useAragon.ts index a2334aa..51fb4ea 100644 --- a/features/aragon/useAragon.ts +++ b/features/aragon/useAragon.ts @@ -53,3 +53,24 @@ export const useGetVoting = () => { [contractWeb3], ); }; + +export const useGetVoterState = () => { + const { contractWeb3 } = useAragon(); + + return useCallback( + async (voteId: number, voter = '') => { + if (contractWeb3 == null) { + return undefined; + } + try { + return await runWithFunctionLogger('Aragon: get voter info', () => + contractWeb3['getVoterState'](voteId, voter), + ); + } catch (e) { + console.error(e); + return undefined; + } + }, + [contractWeb3], + ); +}; diff --git a/features/aragon/vote/aragonVoteForm.tsx b/features/aragon/vote/aragonVoteForm.tsx index b917d4d..114ef8a 100644 --- a/features/aragon/vote/aragonVoteForm.tsx +++ b/features/aragon/vote/aragonVoteForm.tsx @@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form'; import { InputGroupStyled, InputNumber } from 'shared/ui'; import { useEncodeAragonCalldata } from 'features/votingAdapter'; import { ButtonsGroup, Form, Links, LinkWrapper } from '../aragonFormStyles'; -import { useGetVoting } from '../useAragon'; +import { useGetVoterState, useGetVoting } from '../useAragon'; import { VotingLink } from './votingLink'; type AragonFormData = { @@ -13,6 +13,23 @@ type AragonFormData = { success: boolean; }; +enum voterStates { + Absent, // Voter has not voted + Yea, // Voter has voted for + Nay, // Voter has voted against + DelegateYea, // Delegate has voted for on behalf of the voter + DelegateNay, // Delegate has voted against on behalf of the voter +} +/* + * Search for VotePhase on + * https://etherscan.io/address/0x72fb5253ad16307b9e773d2a78cac58e309d5ba4#code + */ +enum votePhases { + Main, + Objection, + Closed, +} + const validateVoteId = (value: string) => { const number = parseInt(value); if (number.toString() !== value) { @@ -38,10 +55,15 @@ export const AragonVoteForm = () => { const encodeCalldata = useEncodeAragonCalldata(); const aragonVote = useAragonVote(activeVesting?.escrow); const getVoting = useGetVoting(); + const getVoterState = useGetVoterState(); const runTransaction = useCallback( async ({ voteId, success }: AragonFormData) => { - const vote = await getVoting(parseInt(voteId)); + const voteIdNum = parseInt(voteId); + const [vote, voterState] = await Promise.all([ + getVoting(voteIdNum), + getVoterState(voteIdNum, activeVesting?.escrow), + ]); if (vote == null) { ToastError(`Voting doesn't exists`); return; @@ -50,19 +72,28 @@ export const AragonVoteForm = () => { ToastError('Voting is closed'); return; } - /* - * Search for VotePhase on - * https://etherscan.io/address/0x72fb5253ad16307b9e773d2a78cac58e309d5ba4#code - */ - if (success && vote?.phase === 1) { + if (success && vote?.phase === votePhases.Objection) { ToastError('Voting is in objection phase'); return; } - - const callData = await encodeCalldata(parseInt(voteId), success); + if (success && voterState == voterStates.Yea) { + ToastError('You have already voted "YES" in this vote'); + return; + } + if (!success && voterState == voterStates.Nay) { + ToastError('You have already voted "NO" in this vote'); + return; + } + const callData = await encodeCalldata(voteIdNum, success); await aragonVote(callData); }, - [getVoting, encodeCalldata, aragonVote], + [ + getVoting, + getVoterState, + activeVesting?.escrow, + encodeCalldata, + aragonVote, + ], ); const handleYesButton = useCallback(() => {