fix: Improve Vote page (#3429)

* introduce useQuorum

introduce LATEST_GOVERNOR_INDEX

* use CurrencyAmounts and fix % logic

* gate useQuorum to mainnet, just to be safe

* comment
This commit is contained in:
Noah Zinsmeister 2022-03-03 16:27:59 -05:00 committed by GitHub
parent 542bf0bf66
commit 4d69c946bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 35 deletions

@ -23,3 +23,5 @@ export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 13
export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId: number]: number } = { export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId: number]: number } = {
1: DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS, 1: DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS,
} }
export const LATEST_GOVERNOR_INDEX = 2

@ -23,6 +23,7 @@ import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme' import { ExternalLink, ThemedText } from 'theme'
import { CreateProposalTabs } from '../../components/NavigationTabs' import { CreateProposalTabs } from '../../components/NavigationTabs'
import { LATEST_GOVERNOR_INDEX } from '../../constants/governance'
import { UNI } from '../../constants/tokens' import { UNI } from '../../constants/tokens'
import AppBody from '../AppBody' import AppBody from '../AppBody'
import { ProposalActionDetail } from './ProposalActionDetail' import { ProposalActionDetail } from './ProposalActionDetail'
@ -88,8 +89,7 @@ export default function CreateProposal() {
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const latestProposalId = useLatestProposalId(account ?? undefined) ?? '0' const latestProposalId = useLatestProposalId(account ?? undefined) ?? '0'
// the first argument below should be the index of the latest governor const latestProposalData = useProposalData(LATEST_GOVERNOR_INDEX, latestProposalId)
const latestProposalData = useProposalData(/* governorIndex */ 2, latestProposalId)
const { votes: availableVotes } = useUserVotes() const { votes: availableVotes } = useUserVotes()
const proposalThreshold: CurrencyAmount<Token> | undefined = useProposalThreshold() const proposalThreshold: CurrencyAmount<Token> | undefined = useProposalThreshold()

@ -1,7 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
// eslint-disable-next-line no-restricted-imports import { Trans } from '@lingui/macro'
import { t, Trans } from '@lingui/macro' import { CurrencyAmount, Fraction, Token } from '@uniswap/sdk-core'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useActiveLocale } from 'hooks/useActiveLocale' import { useActiveLocale } from 'hooks/useActiveLocale'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
@ -34,6 +33,7 @@ import {
ProposalData, ProposalData,
ProposalState, ProposalState,
useProposalData, useProposalData,
useQuorum,
useUserDelegatee, useUserDelegatee,
useUserVotesAsOfBlock, useUserVotesAsOfBlock,
} from '../../state/governance/hooks' } from '../../state/governance/hooks'
@ -100,7 +100,7 @@ const Progress = styled.div<{ status: 'for' | 'against'; percentageString?: stri
height: 4px; height: 4px;
border-radius: 4px; border-radius: 4px;
background-color: ${({ theme, status }) => (status === 'for' ? theme.green1 : theme.red1)}; background-color: ${({ theme, status }) => (status === 'for' ? theme.green1 : theme.red1)};
width: ${({ percentageString }) => percentageString}; width: ${({ percentageString }) => percentageString ?? '0%'};
` `
const MarkDownWrapper = styled.div` const MarkDownWrapper = styled.div`
@ -146,10 +146,14 @@ export default function VotePage({
params: { governorIndex, id }, params: { governorIndex, id },
}, },
}: RouteComponentProps<{ governorIndex: string; id: string }>) { }: RouteComponentProps<{ governorIndex: string; id: string }>) {
const parsedGovernorIndex = Number.parseInt(governorIndex)
const { chainId, account } = useActiveWeb3React() const { chainId, account } = useActiveWeb3React()
const quorumAmount = useQuorum(parsedGovernorIndex)
// get data for this specific proposal // 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 // update vote option based on button interactions
const [voteOption, setVoteOption] = useState<VoteOption | undefined>(undefined) const [voteOption, setVoteOption] = useState<VoteOption | undefined>(undefined)
@ -189,13 +193,11 @@ export default function VotePage({
} }
// get total votes and format percentages for UI // get total votes and format percentages for UI
const totalVotes: number | undefined = proposalData ? proposalData.forCount + proposalData.againstCount : undefined const totalVotes = proposalData?.forCount?.add(proposalData.againstCount)
const forPercentage: string = t`${ const forPercentage = totalVotes
proposalData && totalVotes ? ((proposalData.forCount * 100) / totalVotes).toFixed(0) : '0' ? proposalData?.forCount?.asFraction?.divide(totalVotes.asFraction)?.multiply(100)
} %` : undefined
const againstPercentage: string = t`${ const againstPercentage = forPercentage ? new Fraction(100).subtract(forPercentage) : undefined
proposalData && totalVotes ? ((proposalData.againstCount * 100) / totalVotes).toFixed(0) : '0'
} %`
// only count available votes as of the proposal start block // only count available votes as of the proposal start block
const availableVotes: CurrencyAmount<Token> | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined) const availableVotes: CurrencyAmount<Token> | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined)
@ -321,13 +323,20 @@ export default function VotePage({
<ThemedText.Black fontWeight={600}> <ThemedText.Black fontWeight={600}>
<Trans>For</Trans> <Trans>For</Trans>
</ThemedText.Black> </ThemedText.Black>
{proposalData && (
<ThemedText.Black fontWeight={600}> <ThemedText.Black fontWeight={600}>
{proposalData?.forCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })} {proposalData.forCount.toFixed(0, { groupSeparator: ',' })}
{quorumAmount && (
<span style={{ fontWeight: 400 }}>{` / ${quorumAmount.toExact({
groupSeparator: ',',
})}`}</span>
)}
</ThemedText.Black> </ThemedText.Black>
)}
</WrapSmall> </WrapSmall>
</AutoColumn> </AutoColumn>
<ProgressWrapper> <ProgressWrapper>
<Progress status={'for'} percentageString={forPercentage} /> {forPercentage && <Progress status={'for'} percentageString={`${forPercentage.toFixed(0)}%`} />}
</ProgressWrapper> </ProgressWrapper>
</CardSection> </CardSection>
</StyledDataCard> </StyledDataCard>
@ -338,13 +347,17 @@ export default function VotePage({
<ThemedText.Black fontWeight={600}> <ThemedText.Black fontWeight={600}>
<Trans>Against</Trans> <Trans>Against</Trans>
</ThemedText.Black> </ThemedText.Black>
{proposalData && (
<ThemedText.Black fontWeight={600}> <ThemedText.Black fontWeight={600}>
{proposalData?.againstCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })} {proposalData.againstCount.toFixed(0, { groupSeparator: ',' })}
</ThemedText.Black> </ThemedText.Black>
)}
</WrapSmall> </WrapSmall>
</AutoColumn> </AutoColumn>
<ProgressWrapper> <ProgressWrapper>
<Progress status={'against'} percentageString={againstPercentage} /> {againstPercentage && (
<Progress status={'against'} percentageString={`${againstPercentage.toFixed(0)}%`} />
)}
</ProgressWrapper> </ProgressWrapper>
</CardSection> </CardSection>
</StyledDataCard> </StyledDataCard>

@ -3,18 +3,19 @@ import { isAddress } from '@ethersproject/address'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { toUtf8String, Utf8ErrorFuncs, Utf8ErrorReason } from '@ethersproject/strings' import { toUtf8String, Utf8ErrorFuncs, Utf8ErrorReason } from '@ethersproject/strings'
import { formatUnits } from '@ethersproject/units'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro' import { t } from '@lingui/macro'
import GovernorAlphaJson from '@uniswap/governance/build/GovernorAlpha.json' import GovernorAlphaJson from '@uniswap/governance/build/GovernorAlpha.json'
import UniJson from '@uniswap/governance/build/Uni.json' import UniJson from '@uniswap/governance/build/Uni.json'
import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { ChainId } from '@uniswap/smart-order-router'
import GOVERNOR_BRAVO_ABI from 'abis/governor-bravo.json' import GOVERNOR_BRAVO_ABI from 'abis/governor-bravo.json'
import { import {
GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ALPHA_V0_ADDRESSES,
GOVERNANCE_ALPHA_V1_ADDRESSES, GOVERNANCE_ALPHA_V1_ADDRESSES,
GOVERNANCE_BRAVO_ADDRESSES, GOVERNANCE_BRAVO_ADDRESSES,
} from 'constants/addresses' } from 'constants/addresses'
import { LATEST_GOVERNOR_INDEX } from 'constants/governance'
import { POLYGON_PROPOSAL_TITLE } from 'constants/proposals/polygon_proposal_title' import { POLYGON_PROPOSAL_TITLE } from 'constants/proposals/polygon_proposal_title'
import { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from 'constants/proposals/uniswap_grants_proposal_description' import { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from 'constants/proposals/uniswap_grants_proposal_description'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
@ -39,23 +40,24 @@ import { VoteOption } from './types'
const { abi: GOVERNANCE_ABI } = GovernorAlphaJson const { abi: GOVERNANCE_ABI } = GovernorAlphaJson
const { abi: UNI_ABI } = UniJson const { abi: UNI_ABI } = UniJson
export function useGovernanceV0Contract(): Contract | null { function useGovernanceV0Contract(): Contract | null {
return useContract(GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ABI, false) 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) 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) return useContract(GOVERNANCE_BRAVO_ADDRESSES, GOVERNOR_BRAVO_ABI, true)
} }
export const useLatestGovernanceContract = useGovernanceBravoContract const useLatestGovernanceContract = useGovernanceBravoContract
export function useUniContract() { export function useUniContract() {
const { chainId } = useActiveWeb3React() 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 { interface ProposalDetail {
@ -70,8 +72,8 @@ export interface ProposalData {
description: string description: string
proposer: string proposer: string
status: ProposalState status: ProposalState
forCount: number forCount: CurrencyAmount<Token>
againstCount: number againstCount: CurrencyAmount<Token>
startBlock: number startBlock: number
endBlock: number endBlock: number
details: ProposalDetail[] details: ProposalDetail[]
@ -244,6 +246,8 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean }
const formattedLogsV1 = useFormattedProposalCreatedLogs(gov1, gov1ProposalIndexes) const formattedLogsV1 = useFormattedProposalCreatedLogs(gov1, gov1ProposalIndexes)
const formattedLogsV2 = useFormattedProposalCreatedLogs(gov2, gov2ProposalIndexes) const formattedLogsV2 = useFormattedProposalCreatedLogs(gov2, gov2ProposalIndexes)
const uni = useMemo(() => (chainId ? UNI[chainId] : undefined), [chainId])
// early return until events are fetched // early return until events are fetched
return useMemo(() => { return useMemo(() => {
const proposalsCallData = [...proposalsV0, ...proposalsV1, ...proposalsV2] const proposalsCallData = [...proposalsV0, ...proposalsV1, ...proposalsV2]
@ -251,6 +255,7 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean }
const formattedLogs = [...(formattedLogsV0 ?? []), ...(formattedLogsV1 ?? []), ...(formattedLogsV2 ?? [])] const formattedLogs = [...(formattedLogsV0 ?? []), ...(formattedLogsV1 ?? []), ...(formattedLogsV2 ?? [])]
if ( if (
!uni ||
proposalsCallData.some((p) => p.loading) || proposalsCallData.some((p) => p.loading) ||
proposalStatesCallData.some((p) => p.loading) || proposalStatesCallData.some((p) => p.loading) ||
(gov0 && !formattedLogsV0) || (gov0 && !formattedLogsV0) ||
@ -280,9 +285,8 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean }
description: description ?? t`No description.`, description: description ?? t`No description.`,
proposer: proposal?.result?.proposer, proposer: proposal?.result?.proposer,
status: proposalStatesCallData[i]?.result?.[0] ?? ProposalState.UNDETERMINED, status: proposalStatesCallData[i]?.result?.[0] ?? ProposalState.UNDETERMINED,
forCount: parseFloat(formatUnits(proposal?.result?.forVotes?.toString() ?? 0, 18)), forCount: CurrencyAmount.fromRawAmount(uni, proposal?.result?.forVotes),
againstCount: parseFloat(formatUnits(proposal?.result?.againstVotes?.toString() ?? 0, 18)), againstCount: CurrencyAmount.fromRawAmount(uni, proposal?.result?.againstVotes),
abstainCount: parseFloat(formatUnits(proposal?.result?.abstainVotes?.toString() ?? 0, 18)),
startBlock, startBlock,
endBlock: parseInt(proposal?.result?.endBlock?.toString()), endBlock: parseInt(proposal?.result?.endBlock?.toString()),
details: formattedLogs[i]?.details, details: formattedLogs[i]?.details,
@ -304,6 +308,7 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean }
proposalsV0, proposalsV0,
proposalsV1, proposalsV1,
proposalsV2, 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) return data.filter((p) => p.governorIndex === governorIndex)?.find((p) => p.id === id)
} }
export function useQuorum(governorIndex: number): CurrencyAmount<Token> | 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 // get the users delegatee if it exists
export function useUserDelegatee(): string { export function useUserDelegatee(): string {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
@ -339,7 +362,7 @@ export function useUserVotesAsOfBlock(block: number | undefined): CurrencyAmount
const uniContract = useUniContract() const uniContract = useUniContract()
// check for available votes // 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]) const votes = useSingleCallResult(uniContract, 'getPriorVotes', [account ?? undefined, block ?? undefined])
?.result?.[0] ?.result?.[0]
return votes && uni ? CurrencyAmount.fromRawAmount(uni, votes) : undefined return votes && uni ? CurrencyAmount.fromRawAmount(uni, votes) : undefined
@ -451,7 +474,7 @@ export function useProposalThreshold(): CurrencyAmount<Token> | undefined {
const latestGovernanceContract = useLatestGovernanceContract() const latestGovernanceContract = useLatestGovernanceContract()
const res = useSingleCallResult(latestGovernanceContract, 'proposalThreshold') const res = useSingleCallResult(latestGovernanceContract, 'proposalThreshold')
const uni = chainId ? UNI[chainId] : undefined const uni = useMemo(() => (chainId ? UNI[chainId] : undefined), [chainId])
if (res?.result?.[0] && uni) { if (res?.result?.[0] && uni) {
return CurrencyAmount.fromRawAmount(uni, res.result[0]) return CurrencyAmount.fromRawAmount(uni, res.result[0])