restrict governance UI to mainnet only
fix governor name bug revert useContract change add governorIndex to vote page only fetch latest useLatestProposalCount fix useDataFromEventLogs hardcode proposalIndexes for old governors
This commit is contained in:
parent
d9bd392e6d
commit
b763659788
@ -18,13 +18,19 @@ export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap(
|
||||
)
|
||||
|
||||
// most current governance contract address should always be the 0 index
|
||||
// only support governance on mainnet
|
||||
export const GOVERNANCE_ADDRESSES: AddressMap[] = [
|
||||
{
|
||||
[SupportedChainId.MAINNET]: '0xC4e172459f1E7939D522503B81AFAaC1014CE6F6',
|
||||
},
|
||||
constructSameAddressMap('0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F', false),
|
||||
{
|
||||
[SupportedChainId.MAINNET]: '0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F',
|
||||
},
|
||||
]
|
||||
export const TIMELOCK_ADDRESS: AddressMap = constructSameAddressMap('0x1a9C8182C09F50C8318d769245beA52c32BE35BC', false)
|
||||
export const TIMELOCK_ADDRESS: AddressMap = {
|
||||
[SupportedChainId.MAINNET]: '0x1a9C8182C09F50C8318d769245beA52c32BE35BC',
|
||||
}
|
||||
|
||||
export const MERKLE_DISTRIBUTOR_ADDRESS: AddressMap = {
|
||||
[SupportedChainId.MAINNET]: '0x090D4613473dEE047c3f2706764f49E0821D256e',
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ const governanceContracts = (): Record<string, string> =>
|
||||
GOVERNANCE_ADDRESSES.reduce(
|
||||
(acc, addressMap, i) => ({
|
||||
...acc,
|
||||
[addressMap[SupportedChainId.MAINNET]]: `Governance${i === GOVERNANCE_ADDRESSES.length - 1 ? '' : ` (V${i})`}`,
|
||||
[addressMap[SupportedChainId.MAINNET]]: `Governance${
|
||||
i === 0 ? '' : ` (V${GOVERNANCE_ADDRESSES.length - 1 - i})`
|
||||
}`,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
|
@ -1,2 +1 @@
|
||||
export const UNISWAP_GRANTS_START_BLOCK = 11473815
|
||||
export const EDUCATION_FUND_1_START_BLOCK = 12620175
|
||||
|
@ -46,34 +46,19 @@ import { useActiveWeb3React } from './web3'
|
||||
|
||||
// returns null on errors
|
||||
export function useContract<T extends Contract = Contract>(
|
||||
addressOrAddressMap: string | { [chainId: number]: string } | { [chainId: number]: string }[] | undefined,
|
||||
addressOrAddressMap: string | { [chainId: number]: string } | undefined,
|
||||
ABI: any,
|
||||
withSignerIfPossible = true
|
||||
): T | null {
|
||||
const { library, account, chainId } = useActiveWeb3React()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!addressOrAddressMap || !ABI || !library || !chainId) {
|
||||
return null
|
||||
}
|
||||
if (!addressOrAddressMap || !ABI || !library || !chainId) return null
|
||||
let address: string | undefined
|
||||
if (typeof addressOrAddressMap === 'string') {
|
||||
address = addressOrAddressMap
|
||||
} else if (!Array.isArray(addressOrAddressMap)) {
|
||||
address = addressOrAddressMap[chainId]
|
||||
}
|
||||
if (!address && !Array.isArray(addressOrAddressMap)) {
|
||||
return null
|
||||
}
|
||||
if (typeof addressOrAddressMap === 'string') address = addressOrAddressMap
|
||||
else address = addressOrAddressMap[chainId]
|
||||
if (!address) return null
|
||||
try {
|
||||
if (Array.isArray(addressOrAddressMap)) {
|
||||
return addressOrAddressMap.map((addressMap) =>
|
||||
getContract(addressMap[chainId], ABI, library, withSignerIfPossible && account ? account : undefined)
|
||||
)
|
||||
}
|
||||
if (!address) {
|
||||
return null
|
||||
}
|
||||
return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined)
|
||||
} catch (error) {
|
||||
console.error('Failed to get contract', error)
|
||||
@ -131,21 +116,22 @@ export function useMerkleDistributorContract() {
|
||||
return useContract(MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ABI, true)
|
||||
}
|
||||
|
||||
export function useGovernanceContracts(): Contract[] | null {
|
||||
export function useGovernanceContracts(): (Contract | null)[] {
|
||||
const { library, account, chainId } = useActiveWeb3React()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!library || !chainId) {
|
||||
return null
|
||||
return []
|
||||
}
|
||||
|
||||
return GOVERNANCE_ADDRESSES.filter((addressMap) => Boolean(addressMap[chainId])).map((addressMap) => {
|
||||
try {
|
||||
return GOVERNANCE_ADDRESSES.filter((addressMap) => Boolean(addressMap[chainId])).map((addressMap) =>
|
||||
getContract(addressMap[chainId], GOVERNANCE_ABI, library, account ? account : undefined)
|
||||
)
|
||||
return getContract(addressMap[chainId], GOVERNANCE_ABI, library, account ? account : undefined)
|
||||
} catch (error) {
|
||||
console.error('Failed to get contract', error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
}, [library, chainId, account])
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ export default function App() {
|
||||
<Web3ReactManager>
|
||||
<Switch>
|
||||
<Route exact strict path="/vote" component={Vote} />
|
||||
<Route exact strict path="/vote/:id" component={VotePage} />
|
||||
<Route exact strict path="/vote/:governorIndex/:id" component={VotePage} />
|
||||
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
|
||||
<Route exact strict path="/uni" component={Earn} />
|
||||
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
|
||||
|
@ -121,13 +121,13 @@ const ProposerAddressLink = styled(ExternalLink)`
|
||||
|
||||
export default function VotePage({
|
||||
match: {
|
||||
params: { id },
|
||||
params: { governorIndex, id },
|
||||
},
|
||||
}: RouteComponentProps<{ id: string }>) {
|
||||
}: RouteComponentProps<{ governorIndex: string; id: string }>) {
|
||||
const { chainId, account } = useActiveWeb3React()
|
||||
|
||||
// get data for this specific proposal
|
||||
const proposalData: ProposalData | undefined = useProposalData(id)
|
||||
const proposalData: ProposalData | undefined = useProposalData(Number.parseInt(governorIndex), id)
|
||||
|
||||
// update support based on button interactions
|
||||
const [support, setSupport] = useState<boolean>(true)
|
||||
|
@ -248,9 +248,9 @@ export default function Vote() {
|
||||
</TYPE.subHeader>
|
||||
</EmptyProposals>
|
||||
)}
|
||||
{allProposals?.reverse().map((p: ProposalData, i) => {
|
||||
{allProposals?.reverse()?.map((p: ProposalData) => {
|
||||
return (
|
||||
<Proposal as={Link} to={'/vote/' + p.id} key={i}>
|
||||
<Proposal as={Link} to={`/vote/${p.governorIndex}/${p.id}`} key={`${p.governorIndex}${p.id}`}>
|
||||
<ProposalNumber>{p.id}</ProposalNumber>
|
||||
<ProposalTitle>{p.title}</ProposalTitle>
|
||||
<ProposalStatus status={p.status}>{ProposalState[p.status]}</ProposalStatus>
|
||||
|
@ -2,16 +2,17 @@ import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { abi as GOV_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { GOVERNANCE_ADDRESSES } from 'constants/addresses'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from 'constants/proposals/uniswap_grants_proposal_description'
|
||||
import { ethers, utils } from 'ethers'
|
||||
import { BigNumber, ethers, utils } from 'ethers'
|
||||
import { isAddress } from 'ethers/lib/utils'
|
||||
import { useGovernanceContracts, useUniContract } from 'hooks/useContract'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { calculateGasMargin } from 'utils/calculateGasMargin'
|
||||
import { EDUCATION_FUND_1_START_BLOCK, UNISWAP_GRANTS_START_BLOCK } from '../../constants/proposals'
|
||||
import { UNISWAP_GRANTS_START_BLOCK } from '../../constants/proposals'
|
||||
import { UNI } from '../../constants/tokens'
|
||||
import { useMultipleContractMultipleData, useMultipleContractSingleData, useSingleCallResult } from '../multicall/hooks'
|
||||
import { useMultipleContractMultipleData, useSingleCallResult } from '../multicall/hooks'
|
||||
import { useTransactionAdder } from '../transactions/hooks'
|
||||
|
||||
interface ProposalDetail {
|
||||
@ -31,6 +32,7 @@ export interface ProposalData {
|
||||
startBlock: number
|
||||
endBlock: number
|
||||
details: ProposalDetail[]
|
||||
governorIndex: number // index in the governance address array for which this proposal pertains
|
||||
}
|
||||
|
||||
export enum ProposalState {
|
||||
@ -46,63 +48,68 @@ export enum ProposalState {
|
||||
}
|
||||
|
||||
const GovernanceInterface = new ethers.utils.Interface(GOV_ABI)
|
||||
// get count of all proposals made
|
||||
export function useProposalCounts(): Record<string, number> | undefined {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const addresses = useMemo(() => {
|
||||
if (!chainId) {
|
||||
return []
|
||||
// get count of all proposals made in the latest governor contract
|
||||
function useLatestProposalCount(): number | undefined {
|
||||
const govContracts = useGovernanceContracts()
|
||||
|
||||
const res = useSingleCallResult(govContracts[0], 'proposalCount')
|
||||
|
||||
if (res?.result?.[0]) {
|
||||
return (res.result[0] as BigNumber).toNumber()
|
||||
}
|
||||
return GOVERNANCE_ADDRESSES.map((addressMap) => addressMap[chainId])
|
||||
}, [chainId])
|
||||
const responses = useMultipleContractSingleData(addresses, GovernanceInterface, 'proposalCount')
|
||||
return useMemo(() => {
|
||||
return responses.reduce((acc, response, i) => {
|
||||
if (response.result && !response.loading) {
|
||||
return {
|
||||
...acc,
|
||||
[addresses[i]]: parseInt(response.result[0]),
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}, [addresses, responses])
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Need proposal events to get description data emitted from
|
||||
* new proposal event.
|
||||
*/
|
||||
export function useDataFromEventLogs() {
|
||||
function useDataFromEventLogs():
|
||||
| {
|
||||
description: string
|
||||
details: { target: string; functionSig: string; callData: string }[]
|
||||
}[][]
|
||||
| undefined {
|
||||
const { library, chainId } = useActiveWeb3React()
|
||||
const [formattedEvents, setFormattedEvents] =
|
||||
useState<{ description: string; details: { target: string; functionSig: string; callData: string }[] }[]>()
|
||||
useState<{ description: string; details: { target: string; functionSig: string; callData: string }[] }[][]>()
|
||||
|
||||
const govContracts = useGovernanceContracts()
|
||||
|
||||
// create filter for these specific events
|
||||
// create filters for ProposalCreated events
|
||||
const filters = useMemo(
|
||||
() =>
|
||||
govContracts
|
||||
? govContracts.map((contract) => ({
|
||||
govContracts?.filter((govContract) => !!govContract)?.length > 0
|
||||
? govContracts
|
||||
.filter((govContract): govContract is ethers.Contract => !!govContract)
|
||||
.map((contract) => ({
|
||||
...contract.filters.ProposalCreated(),
|
||||
fromBlock: 10861678,
|
||||
fromBlock: 10861678, // TODO could optimize this on a per-contract basis, this is the safe value
|
||||
toBlock: 'latest',
|
||||
}))
|
||||
: undefined,
|
||||
[govContracts]
|
||||
)
|
||||
|
||||
// clear logs on chainId change
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setFormattedEvents(undefined)
|
||||
}
|
||||
}, [chainId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!filters || !library) return
|
||||
let stale = false
|
||||
|
||||
if (!formattedEvents) {
|
||||
const filterRequests = filters.map((filter) => library.getLogs(filter))
|
||||
Promise.all(filterRequests)
|
||||
.then((events) => events.flat())
|
||||
Promise.all(filters.map((filter) => library.getLogs(filter)))
|
||||
.then((governanceContractsProposalEvents) => {
|
||||
if (stale) return
|
||||
const formattedEventData = governanceContractsProposalEvents.map((event) => {
|
||||
|
||||
const formattedEventData = governanceContractsProposalEvents.map((proposalEvents) => {
|
||||
return proposalEvents.map((event) => {
|
||||
const eventParsed = GovernanceInterface.parseLog(event).args
|
||||
return {
|
||||
description: eventParsed.description,
|
||||
@ -119,11 +126,17 @@ export function useDataFromEventLogs() {
|
||||
}),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
setFormattedEvents(formattedEventData)
|
||||
})
|
||||
.catch((error) => {
|
||||
if (stale) return
|
||||
|
||||
console.error('Failed to fetch proposals', error)
|
||||
setFormattedEvents(undefined)
|
||||
})
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
@ -136,41 +149,22 @@ export function useDataFromEventLogs() {
|
||||
}
|
||||
|
||||
// get data for all past and active proposals
|
||||
export function useAllProposalData() {
|
||||
export function useAllProposalData(): ProposalData[] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const proposalCounts = useProposalCounts()
|
||||
|
||||
const proposalIndexes = useMemo(() => {
|
||||
const results: number[][][] = []
|
||||
const emptyState = new Array(GOVERNANCE_ADDRESSES.length).fill([], 0)
|
||||
GOVERNANCE_ADDRESSES.forEach((addressMap, i) => {
|
||||
results[i] = []
|
||||
if (!chainId) {
|
||||
return emptyState
|
||||
}
|
||||
const address = addressMap[chainId]
|
||||
if (!proposalCounts || proposalCounts[address] === undefined) {
|
||||
return emptyState
|
||||
}
|
||||
for (let j = 1; j <= proposalCounts[address]; j++) {
|
||||
results[i].push([j])
|
||||
}
|
||||
return results
|
||||
})
|
||||
return results.filter((indexArray) => indexArray.length > 0)
|
||||
}, [chainId, proposalCounts])
|
||||
const proposalCount = useLatestProposalCount()
|
||||
|
||||
const addresses = useMemo(() => {
|
||||
if (!chainId) {
|
||||
return []
|
||||
}
|
||||
return GOVERNANCE_ADDRESSES.map((addressMap) => addressMap[chainId]).filter(
|
||||
(address) => proposalCounts && proposalCounts[address] > 0
|
||||
)
|
||||
}, [chainId, proposalCounts])
|
||||
return chainId === SupportedChainId.MAINNET ? GOVERNANCE_ADDRESSES.map((addressMap) => addressMap[chainId]) : []
|
||||
}, [chainId])
|
||||
|
||||
// get metadata from past events
|
||||
const formattedEvents = useDataFromEventLogs()
|
||||
const proposalIndexes = useMemo(() => {
|
||||
return chainId === SupportedChainId.MAINNET
|
||||
? [
|
||||
typeof proposalCount === 'number' ? new Array(proposalCount).fill(0).map((_, i) => [i + 1]) : [], // dynamic for current governor alpha
|
||||
[[1], [2], [3], [4]], // hardcoded for governor alpha V0
|
||||
]
|
||||
: []
|
||||
}, [chainId, proposalCount])
|
||||
|
||||
// get all proposal entities
|
||||
const allProposalsCallData = useMultipleContractMultipleData(
|
||||
@ -178,7 +172,7 @@ export function useAllProposalData() {
|
||||
GovernanceInterface,
|
||||
'proposals',
|
||||
proposalIndexes
|
||||
).flat()
|
||||
)
|
||||
|
||||
// get all proposal states
|
||||
const allProposalStatesCallData = useMultipleContractMultipleData(
|
||||
@ -186,20 +180,36 @@ export function useAllProposalData() {
|
||||
GovernanceInterface,
|
||||
'state',
|
||||
proposalIndexes
|
||||
).flat()
|
||||
)
|
||||
|
||||
// get metadata from past events
|
||||
const allFormattedEvents = useDataFromEventLogs()
|
||||
|
||||
// early return until events are fetched
|
||||
if (!allFormattedEvents) return []
|
||||
|
||||
const results: ProposalData[][] = []
|
||||
|
||||
for (
|
||||
let governanceContractIndex = 0;
|
||||
governanceContractIndex < allProposalsCallData.length;
|
||||
governanceContractIndex++
|
||||
) {
|
||||
const proposalsCallData = allProposalsCallData[governanceContractIndex]
|
||||
const proposalStatesCallData = allProposalStatesCallData[governanceContractIndex]
|
||||
const formattedEvents = allFormattedEvents[governanceContractIndex]
|
||||
|
||||
if (
|
||||
!allProposalsCallData?.every((p) => Boolean(p.result)) ||
|
||||
!allProposalStatesCallData?.every((p) => Boolean(p.result)) ||
|
||||
!proposalsCallData?.every((p) => Boolean(p.result)) ||
|
||||
!proposalStatesCallData?.every((p) => Boolean(p.result)) ||
|
||||
!formattedEvents?.every((p) => Boolean(p))
|
||||
) {
|
||||
return []
|
||||
results.push([])
|
||||
continue
|
||||
}
|
||||
|
||||
const omittedProposalStartBlocks = [EDUCATION_FUND_1_START_BLOCK]
|
||||
|
||||
return allProposalsCallData
|
||||
.map((proposal, i) => {
|
||||
results.push(
|
||||
proposalsCallData.map((proposal, i) => {
|
||||
let description = formattedEvents[i].description
|
||||
const startBlock = parseInt(proposal?.result?.startBlock?.toString())
|
||||
if (startBlock === UNISWAP_GRANTS_START_BLOCK) {
|
||||
@ -210,20 +220,24 @@ export function useAllProposalData() {
|
||||
title: description?.split(/# |\n/g)[1] ?? 'Untitled',
|
||||
description: description ?? 'No description.',
|
||||
proposer: proposal?.result?.proposer,
|
||||
status: allProposalStatesCallData[i]?.result?.[0] ?? ProposalState.Undetermined,
|
||||
status: proposalStatesCallData[i]?.result?.[0] ?? ProposalState.Undetermined,
|
||||
forCount: parseFloat(ethers.utils.formatUnits(proposal?.result?.forVotes.toString(), 18)),
|
||||
againstCount: parseFloat(ethers.utils.formatUnits(proposal?.result?.againstVotes.toString(), 18)),
|
||||
startBlock,
|
||||
endBlock: parseInt(proposal?.result?.endBlock?.toString()),
|
||||
details: formattedEvents[i].details,
|
||||
governorIndex: i,
|
||||
}
|
||||
})
|
||||
.filter((proposal) => !omittedProposalStartBlocks.includes(proposal.startBlock))
|
||||
)
|
||||
}
|
||||
|
||||
return results.flat()
|
||||
}
|
||||
|
||||
export function useProposalData(id: string): ProposalData | undefined {
|
||||
export function useProposalData(governorIndex: number, id: string): ProposalData | undefined {
|
||||
const allProposalData = useAllProposalData()
|
||||
return allProposalData?.find((p) => p.id === id)
|
||||
return allProposalData?.filter((p) => p.governorIndex === governorIndex)?.find((p) => p.id === id)
|
||||
}
|
||||
|
||||
// get the users delegatee if it exists
|
||||
|
Loading…
Reference in New Issue
Block a user