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:
parent
542bf0bf66
commit
4d69c946bf
@ -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])
|
||||||
|
Loading…
Reference in New Issue
Block a user