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 } = {
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 { 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<Token> | undefined = useProposalThreshold()

@ -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<VoteOption | undefined>(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<Token> | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined)
@ -321,13 +323,20 @@ export default function VotePage({
<ThemedText.Black fontWeight={600}>
<Trans>For</Trans>
</ThemedText.Black>
<ThemedText.Black fontWeight={600}>
{proposalData?.forCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })}
</ThemedText.Black>
{proposalData && (
<ThemedText.Black fontWeight={600}>
{proposalData.forCount.toFixed(0, { groupSeparator: ',' })}
{quorumAmount && (
<span style={{ fontWeight: 400 }}>{` / ${quorumAmount.toExact({
groupSeparator: ',',
})}`}</span>
)}
</ThemedText.Black>
)}
</WrapSmall>
</AutoColumn>
<ProgressWrapper>
<Progress status={'for'} percentageString={forPercentage} />
{forPercentage && <Progress status={'for'} percentageString={`${forPercentage.toFixed(0)}%`} />}
</ProgressWrapper>
</CardSection>
</StyledDataCard>
@ -338,13 +347,17 @@ export default function VotePage({
<ThemedText.Black fontWeight={600}>
<Trans>Against</Trans>
</ThemedText.Black>
<ThemedText.Black fontWeight={600}>
{proposalData?.againstCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })}
</ThemedText.Black>
{proposalData && (
<ThemedText.Black fontWeight={600}>
{proposalData.againstCount.toFixed(0, { groupSeparator: ',' })}
</ThemedText.Black>
)}
</WrapSmall>
</AutoColumn>
<ProgressWrapper>
<Progress status={'against'} percentageString={againstPercentage} />
{againstPercentage && (
<Progress status={'against'} percentageString={`${againstPercentage.toFixed(0)}%`} />
)}
</ProgressWrapper>
</CardSection>
</StyledDataCard>

@ -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<Token>
againstCount: CurrencyAmount<Token>
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<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
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<Token> | 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])