diff --git a/src/constants/governance.ts b/src/constants/governance.ts index 15c0707c09..a1e2a1139d 100644 --- a/src/constants/governance.ts +++ b/src/constants/governance.ts @@ -23,3 +23,5 @@ export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 13 export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId: number]: number } = { 1: DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS, } + +export const LATEST_GOVERNOR_INDEX = 2 diff --git a/src/pages/CreateProposal/index.tsx b/src/pages/CreateProposal/index.tsx index f206430669..d10479dffc 100644 --- a/src/pages/CreateProposal/index.tsx +++ b/src/pages/CreateProposal/index.tsx @@ -23,6 +23,7 @@ import styled from 'styled-components/macro' import { ExternalLink, ThemedText } from 'theme' import { CreateProposalTabs } from '../../components/NavigationTabs' +import { LATEST_GOVERNOR_INDEX } from '../../constants/governance' import { UNI } from '../../constants/tokens' import AppBody from '../AppBody' import { ProposalActionDetail } from './ProposalActionDetail' @@ -88,8 +89,7 @@ export default function CreateProposal() { const { account, chainId } = useActiveWeb3React() const latestProposalId = useLatestProposalId(account ?? undefined) ?? '0' - // the first argument below should be the index of the latest governor - const latestProposalData = useProposalData(/* governorIndex */ 2, latestProposalId) + const latestProposalData = useProposalData(LATEST_GOVERNOR_INDEX, latestProposalId) const { votes: availableVotes } = useUserVotes() const proposalThreshold: CurrencyAmount | undefined = useProposalThreshold() diff --git a/src/pages/Vote/VotePage.tsx b/src/pages/Vote/VotePage.tsx index 2f96ef775d..1f80c6cc4a 100644 --- a/src/pages/Vote/VotePage.tsx +++ b/src/pages/Vote/VotePage.tsx @@ -1,7 +1,6 @@ import { BigNumber } from '@ethersproject/bignumber' -// eslint-disable-next-line no-restricted-imports -import { t, Trans } from '@lingui/macro' -import { CurrencyAmount, Token } from '@uniswap/sdk-core' +import { Trans } from '@lingui/macro' +import { CurrencyAmount, Fraction, Token } from '@uniswap/sdk-core' import { useActiveLocale } from 'hooks/useActiveLocale' import useActiveWeb3React from 'hooks/useActiveWeb3React' import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' @@ -34,6 +33,7 @@ import { ProposalData, ProposalState, useProposalData, + useQuorum, useUserDelegatee, useUserVotesAsOfBlock, } from '../../state/governance/hooks' @@ -100,7 +100,7 @@ const Progress = styled.div<{ status: 'for' | 'against'; percentageString?: stri height: 4px; border-radius: 4px; background-color: ${({ theme, status }) => (status === 'for' ? theme.green1 : theme.red1)}; - width: ${({ percentageString }) => percentageString}; + width: ${({ percentageString }) => percentageString ?? '0%'}; ` const MarkDownWrapper = styled.div` @@ -146,10 +146,14 @@ export default function VotePage({ params: { governorIndex, id }, }, }: RouteComponentProps<{ governorIndex: string; id: string }>) { + const parsedGovernorIndex = Number.parseInt(governorIndex) + const { chainId, account } = useActiveWeb3React() + const quorumAmount = useQuorum(parsedGovernorIndex) + // get data for this specific proposal - const proposalData: ProposalData | undefined = useProposalData(Number.parseInt(governorIndex), id) + const proposalData: ProposalData | undefined = useProposalData(parsedGovernorIndex, id) // update vote option based on button interactions const [voteOption, setVoteOption] = useState(undefined) @@ -189,13 +193,11 @@ export default function VotePage({ } // get total votes and format percentages for UI - const totalVotes: number | undefined = proposalData ? proposalData.forCount + proposalData.againstCount : undefined - const forPercentage: string = t`${ - proposalData && totalVotes ? ((proposalData.forCount * 100) / totalVotes).toFixed(0) : '0' - } %` - const againstPercentage: string = t`${ - proposalData && totalVotes ? ((proposalData.againstCount * 100) / totalVotes).toFixed(0) : '0' - } %` + const totalVotes = proposalData?.forCount?.add(proposalData.againstCount) + const forPercentage = totalVotes + ? proposalData?.forCount?.asFraction?.divide(totalVotes.asFraction)?.multiply(100) + : undefined + const againstPercentage = forPercentage ? new Fraction(100).subtract(forPercentage) : undefined // only count available votes as of the proposal start block const availableVotes: CurrencyAmount | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined) @@ -321,13 +323,20 @@ export default function VotePage({ For - - {proposalData?.forCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })} - + {proposalData && ( + + {proposalData.forCount.toFixed(0, { groupSeparator: ',' })} + {quorumAmount && ( + {` / ${quorumAmount.toExact({ + groupSeparator: ',', + })}`} + )} + + )} - + {forPercentage && } @@ -338,13 +347,17 @@ export default function VotePage({ Against - - {proposalData?.againstCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })} - + {proposalData && ( + + {proposalData.againstCount.toFixed(0, { groupSeparator: ',' })} + + )} - + {againstPercentage && ( + + )} diff --git a/src/state/governance/hooks.ts b/src/state/governance/hooks.ts index d760bb36c4..dde9272582 100644 --- a/src/state/governance/hooks.ts +++ b/src/state/governance/hooks.ts @@ -3,18 +3,19 @@ import { isAddress } from '@ethersproject/address' import { Contract } from '@ethersproject/contracts' import { TransactionResponse } from '@ethersproject/providers' import { toUtf8String, Utf8ErrorFuncs, Utf8ErrorReason } from '@ethersproject/strings' -import { formatUnits } from '@ethersproject/units' // eslint-disable-next-line no-restricted-imports import { t } from '@lingui/macro' import GovernorAlphaJson from '@uniswap/governance/build/GovernorAlpha.json' import UniJson from '@uniswap/governance/build/Uni.json' import { CurrencyAmount, Token } from '@uniswap/sdk-core' +import { ChainId } from '@uniswap/smart-order-router' import GOVERNOR_BRAVO_ABI from 'abis/governor-bravo.json' import { GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ALPHA_V1_ADDRESSES, GOVERNANCE_BRAVO_ADDRESSES, } from 'constants/addresses' +import { LATEST_GOVERNOR_INDEX } from 'constants/governance' import { POLYGON_PROPOSAL_TITLE } from 'constants/proposals/polygon_proposal_title' import { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from 'constants/proposals/uniswap_grants_proposal_description' import useActiveWeb3React from 'hooks/useActiveWeb3React' @@ -39,23 +40,24 @@ import { VoteOption } from './types' const { abi: GOVERNANCE_ABI } = GovernorAlphaJson const { abi: UNI_ABI } = UniJson -export function useGovernanceV0Contract(): Contract | null { +function useGovernanceV0Contract(): Contract | null { return useContract(GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ABI, false) } -export function useGovernanceV1Contract(): Contract | null { +function useGovernanceV1Contract(): Contract | null { return useContract(GOVERNANCE_ALPHA_V1_ADDRESSES, GOVERNANCE_ABI, false) } -export function useGovernanceBravoContract(): Contract | null { +function useGovernanceBravoContract(): Contract | null { return useContract(GOVERNANCE_BRAVO_ADDRESSES, GOVERNOR_BRAVO_ABI, true) } -export const useLatestGovernanceContract = useGovernanceBravoContract +const useLatestGovernanceContract = useGovernanceBravoContract export function useUniContract() { const { chainId } = useActiveWeb3React() - return useContract(chainId ? UNI[chainId]?.address : undefined, UNI_ABI, true) + const uniAddress = useMemo(() => (chainId ? UNI[chainId]?.address : undefined), [chainId]) + return useContract(uniAddress, UNI_ABI, true) } interface ProposalDetail { @@ -70,8 +72,8 @@ export interface ProposalData { description: string proposer: string status: ProposalState - forCount: number - againstCount: number + forCount: CurrencyAmount + againstCount: CurrencyAmount startBlock: number endBlock: number details: ProposalDetail[] @@ -244,6 +246,8 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean } const formattedLogsV1 = useFormattedProposalCreatedLogs(gov1, gov1ProposalIndexes) const formattedLogsV2 = useFormattedProposalCreatedLogs(gov2, gov2ProposalIndexes) + const uni = useMemo(() => (chainId ? UNI[chainId] : undefined), [chainId]) + // early return until events are fetched return useMemo(() => { const proposalsCallData = [...proposalsV0, ...proposalsV1, ...proposalsV2] @@ -251,6 +255,7 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean } const formattedLogs = [...(formattedLogsV0 ?? []), ...(formattedLogsV1 ?? []), ...(formattedLogsV2 ?? [])] if ( + !uni || proposalsCallData.some((p) => p.loading) || proposalStatesCallData.some((p) => p.loading) || (gov0 && !formattedLogsV0) || @@ -280,9 +285,8 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean } description: description ?? t`No description.`, proposer: proposal?.result?.proposer, status: proposalStatesCallData[i]?.result?.[0] ?? ProposalState.UNDETERMINED, - forCount: parseFloat(formatUnits(proposal?.result?.forVotes?.toString() ?? 0, 18)), - againstCount: parseFloat(formatUnits(proposal?.result?.againstVotes?.toString() ?? 0, 18)), - abstainCount: parseFloat(formatUnits(proposal?.result?.abstainVotes?.toString() ?? 0, 18)), + forCount: CurrencyAmount.fromRawAmount(uni, proposal?.result?.forVotes), + againstCount: CurrencyAmount.fromRawAmount(uni, proposal?.result?.againstVotes), startBlock, endBlock: parseInt(proposal?.result?.endBlock?.toString()), details: formattedLogs[i]?.details, @@ -304,6 +308,7 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean } proposalsV0, proposalsV1, proposalsV2, + uni, ]) } @@ -312,6 +317,24 @@ export function useProposalData(governorIndex: number, id: string): ProposalData return data.filter((p) => p.governorIndex === governorIndex)?.find((p) => p.id === id) } +export function useQuorum(governorIndex: number): CurrencyAmount | undefined { + const latestGovernanceContract = useLatestGovernanceContract() + const quorumVotes = useSingleCallResult(latestGovernanceContract, 'quorumVotes')?.result?.[0] + const { chainId } = useActiveWeb3React() + const uni = useMemo(() => (chainId ? UNI[chainId] : undefined), [chainId]) + + if ( + !latestGovernanceContract || + !quorumVotes || + chainId !== ChainId.MAINNET || + !uni || + governorIndex !== LATEST_GOVERNOR_INDEX + ) + return undefined + + return CurrencyAmount.fromRawAmount(uni, quorumVotes) +} + // get the users delegatee if it exists export function useUserDelegatee(): string { const { account } = useActiveWeb3React() @@ -339,7 +362,7 @@ export function useUserVotesAsOfBlock(block: number | undefined): CurrencyAmount const uniContract = useUniContract() // check for available votes - const uni = chainId ? UNI[chainId] : undefined + const uni = useMemo(() => (chainId ? UNI[chainId] : undefined), [chainId]) const votes = useSingleCallResult(uniContract, 'getPriorVotes', [account ?? undefined, block ?? undefined]) ?.result?.[0] return votes && uni ? CurrencyAmount.fromRawAmount(uni, votes) : undefined @@ -451,7 +474,7 @@ export function useProposalThreshold(): CurrencyAmount | undefined { const latestGovernanceContract = useLatestGovernanceContract() const res = useSingleCallResult(latestGovernanceContract, 'proposalThreshold') - const uni = chainId ? UNI[chainId] : undefined + const uni = useMemo(() => (chainId ? UNI[chainId] : undefined), [chainId]) if (res?.result?.[0] && uni) { return CurrencyAmount.fromRawAmount(uni, res.result[0])