Commit 014595cd authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

feat: add multi-contract governance support (#1860)

* Revert "feat: quick fix for new governor"
This reverts commit 5dd1249d.

* support multiple governance contracts
parent 1b27d8da
import { Trans } from '@lingui/macro'
import useScrollPosition from '@react-hook/window-scroll' import useScrollPosition from '@react-hook/window-scroll'
import React, { useState } from 'react'
import { Text } from 'rebass'
import { NavLink } from 'react-router-dom'
import { darken } from 'polished' import { darken } from 'polished'
import { Trans } from '@lingui/macro' import React, { useState } from 'react'
import { Moon, Sun } from 'react-feather' import { Moon, Sun } from 'react-feather'
import { NavLink } from 'react-router-dom'
import { Text } from 'rebass'
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
import { useUserHasAvailableClaim } from 'state/claim/hooks'
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { useDarkModeManager } from 'state/user/hooks'
import { useETHBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import Logo from '../../assets/svg/logo.svg' import Logo from '../../assets/svg/logo.svg'
import LogoDark from '../../assets/svg/logo_white.svg' import LogoDark from '../../assets/svg/logo_white.svg'
import { SupportedChainId } from '../../constants/chains' import { NETWORK_LABELS, SupportedChainId } from '../../constants/chains'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { useDarkModeManager } from '../../state/user/hooks' import { ExternalLink, TYPE } from '../../theme'
import { useETHBalances } from '../../state/wallet/hooks'
import { CardNoise } from '../earn/styled'
import { TYPE, ExternalLink } from '../../theme'
import { YellowCard } from '../Card' import { YellowCard } from '../Card'
import ClaimModal from '../claim/ClaimModal'
import { CardNoise } from '../earn/styled'
import Menu from '../Menu' import Menu from '../Menu'
import Modal from '../Modal'
import Row, { RowFixed } from '../Row' import Row, { RowFixed } from '../Row'
import Web3Status from '../Web3Status'
import ClaimModal from '../claim/ClaimModal'
import { useToggleSelfClaimModal, useShowClaimPopup } from '../../state/application/hooks'
import { useUserHasAvailableClaim } from '../../state/claim/hooks'
import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
import { Dots } from '../swap/styleds' import { Dots } from '../swap/styleds'
import Modal from '../Modal' import Web3Status from '../Web3Status'
import UniBalanceContent from './UniBalanceContent' import UniBalanceContent from './UniBalanceContent'
const HeaderFrame = styled.div<{ showBackground: boolean }>` const HeaderFrame = styled.div<{ showBackground: boolean }>`
...@@ -302,16 +298,6 @@ export const StyledMenuButton = styled.button` ...@@ -302,16 +298,6 @@ export const StyledMenuButton = styled.button`
} }
` `
const NETWORK_LABELS: { [chainId in SupportedChainId | number]: string } = {
[SupportedChainId.MAINNET]: 'Mainnet',
[SupportedChainId.RINKEBY]: 'Rinkeby',
[SupportedChainId.ROPSTEN]: 'Ropsten',
[SupportedChainId.GOERLI]: 'Görli',
[SupportedChainId.KOVAN]: 'Kovan',
[SupportedChainId.ARBITRUM_KOVAN]: 'kArbitrum',
[SupportedChainId.ARBITRUM_ONE]: 'Arbitrum One',
}
export default function Header() { export default function Header() {
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
......
...@@ -16,11 +16,13 @@ export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap( ...@@ -16,11 +16,13 @@ export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap(
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
false false
) )
// most current governance contract address should always be the 0 index
export const GOVERNANCE_ADDRESSES: AddressMap[] = [ export const GOVERNANCE_ADDRESSES: AddressMap[] = [
constructSameAddressMap('0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F', false),
{ {
[SupportedChainId.MAINNET]: '0xC4e172459f1E7939D522503B81AFAaC1014CE6F6', [SupportedChainId.MAINNET]: '0xC4e172459f1E7939D522503B81AFAaC1014CE6F6',
}, },
constructSameAddressMap('0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F', false),
] ]
export const TIMELOCK_ADDRESS: AddressMap = constructSameAddressMap('0x1a9C8182C09F50C8318d769245beA52c32BE35BC', false) export const TIMELOCK_ADDRESS: AddressMap = constructSameAddressMap('0x1a9C8182C09F50C8318d769245beA52c32BE35BC', false)
export const MERKLE_DISTRIBUTOR_ADDRESS: AddressMap = { export const MERKLE_DISTRIBUTOR_ADDRESS: AddressMap = {
......
...@@ -7,3 +7,13 @@ export enum SupportedChainId { ...@@ -7,3 +7,13 @@ export enum SupportedChainId {
ARBITRUM_KOVAN = 144545313136048, ARBITRUM_KOVAN = 144545313136048,
ARBITRUM_ONE = 42161, ARBITRUM_ONE = 42161,
} }
export const NETWORK_LABELS: { [chainId in SupportedChainId | number]: string } = {
[SupportedChainId.MAINNET]: 'Mainnet',
[SupportedChainId.RINKEBY]: 'Rinkeby',
[SupportedChainId.ROPSTEN]: 'Ropsten',
[SupportedChainId.GOERLI]: 'Görli',
[SupportedChainId.KOVAN]: 'Kovan',
[SupportedChainId.ARBITRUM_KOVAN]: 'kArbitrum',
[SupportedChainId.ARBITRUM_ONE]: 'Arbitrum One',
}
import { GOVERNANCE_ADDRESSES, TIMELOCK_ADDRESS, UNI_ADDRESS } from './addresses' import { GOVERNANCE_ADDRESSES, TIMELOCK_ADDRESS, UNI_ADDRESS } from './addresses'
import { SupportedChainId } from './chains'
export const COMMON_CONTRACT_NAMES: { [chainId: number]: { [address: string]: string } } = { // returns { [address]: `Governance (V${n})`} for each address in GOVERNANCE_ADDRESSES except the current, which gets no version indicator
[1]: { const governanceContracts = (): Record<string, string> =>
[UNI_ADDRESS[1]]: 'UNI', GOVERNANCE_ADDRESSES.reduce(
[GOVERNANCE_ADDRESSES[0][1]]: 'Governance (V0)', (acc, addressMap, i) => ({
[GOVERNANCE_ADDRESSES[1][1]]: 'Governance', ...acc,
[TIMELOCK_ADDRESS[1]]: 'Timelock', [addressMap[SupportedChainId.MAINNET]]: `Governance${i === GOVERNANCE_ADDRESSES.length - 1 ? '' : ` (V${i})`}`,
}),
{}
)
export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }> = {
[SupportedChainId.MAINNET]: {
[UNI_ADDRESS[SupportedChainId.MAINNET]]: 'UNI',
[TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock',
...governanceContracts(),
}, },
} }
......
export const UNISWAP_GRANTS_START_BLOCK = 11473815
export const EDUCATION_FUND_1_START_BLOCK = 12620175
...@@ -46,19 +46,34 @@ import { useActiveWeb3React } from './web3' ...@@ -46,19 +46,34 @@ import { useActiveWeb3React } from './web3'
// returns null on errors // returns null on errors
export function useContract<T extends Contract = Contract>( export function useContract<T extends Contract = Contract>(
addressOrAddressMap: string | { [chainId: number]: string } | undefined, addressOrAddressMap: string | { [chainId: number]: string } | { [chainId: number]: string }[] | undefined,
ABI: any, ABI: any,
withSignerIfPossible = true withSignerIfPossible = true
): T | null { ): T | null {
const { library, account, chainId } = useActiveWeb3React() const { library, account, chainId } = useActiveWeb3React()
return useMemo(() => { return useMemo(() => {
if (!addressOrAddressMap || !ABI || !library || !chainId) return null if (!addressOrAddressMap || !ABI || !library || !chainId) {
return null
}
let address: string | undefined let address: string | undefined
if (typeof addressOrAddressMap === 'string') address = addressOrAddressMap if (typeof addressOrAddressMap === 'string') {
else address = addressOrAddressMap[chainId] address = addressOrAddressMap
if (!address) return null } else if (!Array.isArray(addressOrAddressMap)) {
address = addressOrAddressMap[chainId]
}
if (!address && !Array.isArray(addressOrAddressMap)) {
return null
}
try { 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) return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined)
} catch (error) { } catch (error) {
console.error('Failed to get contract', error) console.error('Failed to get contract', error)
...@@ -116,11 +131,22 @@ export function useMerkleDistributorContract() { ...@@ -116,11 +131,22 @@ export function useMerkleDistributorContract() {
return useContract(MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ABI, true) return useContract(MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ABI, true)
} }
export function useGovernanceContracts(): (Contract | null)[] { export function useGovernanceContracts(): Contract[] | null {
return [ const { library, account, chainId } = useActiveWeb3React()
useContract(GOVERNANCE_ADDRESSES[0], GOVERNANCE_ABI, false),
useContract(GOVERNANCE_ADDRESSES[1], GOVERNANCE_ABI, true), return useMemo(() => {
] if (!library || !chainId) {
return null
}
try {
return GOVERNANCE_ADDRESSES.filter((addressMap) => Boolean(addressMap[chainId])).map((addressMap) =>
getContract(addressMap[chainId], GOVERNANCE_ABI, library, account ? account : undefined)
)
} catch (error) {
console.error('Failed to get contract', error)
return null
}
}, [library, chainId, account])
} }
export function useUniContract() { export function useUniContract() {
......
...@@ -120,8 +120,7 @@ export default function Vote() { ...@@ -120,8 +120,7 @@ export default function Vote() {
const toggleDelegateModal = useToggleDelegateModal() const toggleDelegateModal = useToggleDelegateModal()
// get data to list all proposals // get data to list all proposals
// TODO don't hardcode for first gov alpha const allProposals: ProposalData[] = useAllProposalData()
const allProposals: ProposalData[] = useAllProposalData()[0]
// user data // user data
const availableVotes: CurrencyAmount<Token> | undefined = useUserVotes() const availableVotes: CurrencyAmount<Token> | undefined = useUserVotes()
...@@ -249,7 +248,7 @@ export default function Vote() { ...@@ -249,7 +248,7 @@ export default function Vote() {
</TYPE.subHeader> </TYPE.subHeader>
</EmptyProposals> </EmptyProposals>
)} )}
{allProposals?.reverse()?.map((p: ProposalData, i) => { {allProposals?.reverse().map((p: ProposalData, i) => {
return ( return (
<Proposal as={Link} to={'/vote/' + p.id} key={i}> <Proposal as={Link} to={'/vote/' + p.id} key={i}>
<ProposalNumber>{p.id}</ProposalNumber> <ProposalNumber>{p.id}</ProposalNumber>
...@@ -260,7 +259,7 @@ export default function Vote() { ...@@ -260,7 +259,7 @@ export default function Vote() {
})} })}
</TopSection> </TopSection>
<TYPE.subHeader color="text3"> <TYPE.subHeader color="text3">
<Trans>A minimum threshold of 1% of the total UNI supply is required to submit proposals</Trans> <Trans>A minimum threshold of 0.25% of the total UNI supply is required to submit proposals</Trans>
</TYPE.subHeader> </TYPE.subHeader>
</PageWrapper> </PageWrapper>
<SwitchLocaleLink /> <SwitchLocaleLink />
......
This diff is collapsed.
import { Interface, FunctionFragment } from '@ethersproject/abi' import { FunctionFragment, Interface } from '@ethersproject/abi'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
...@@ -8,10 +8,10 @@ import { useBlockNumber } from '../application/hooks' ...@@ -8,10 +8,10 @@ import { useBlockNumber } from '../application/hooks'
import { import {
addMulticallListeners, addMulticallListeners,
Call, Call,
removeMulticallListeners, ListenerOptions,
parseCallKey, parseCallKey,
removeMulticallListeners,
toCallKey, toCallKey,
ListenerOptions,
} from './actions' } from './actions'
export interface Result extends ReadonlyArray<any> { export interface Result extends ReadonlyArray<any> {
...@@ -231,6 +231,63 @@ export function useMultipleContractSingleData( ...@@ -231,6 +231,63 @@ export function useMultipleContractSingleData(
}, [fragment, results, contractInterface, latestBlockNumber]) }, [fragment, results, contractInterface, latestBlockNumber])
} }
export function useMultipleContractMultipleData(
addresses: (string | undefined)[],
contractInterface: Interface,
methodName: string,
callInputs?: OptionalMethodInputs[][],
options?: ListenerOptions,
gasRequired?: number
): CallState[][] {
const fragment = useMemo(() => contractInterface.getFunction(methodName), [contractInterface, methodName])
const calls = useMemo(() => {
return addresses.map((address, i) => {
const passesChecks = address && contractInterface && fragment
if (!passesChecks) {
return []
}
return callInputs
? callInputs[i].map<Call | undefined>((inputs) => {
const callData: string | undefined = isValidMethodArgs(inputs)
? contractInterface.encodeFunctionData(fragment, inputs)
: undefined
return address && callData
? {
address,
callData,
...(gasRequired ? { gasRequired } : {}),
}
: undefined
})
: []
})
}, [addresses, callInputs, contractInterface, fragment, gasRequired])
const callResults = useCallsData(calls.flat(), options)
const latestBlockNumber = useBlockNumber()
return useMemo(() => {
const callInputLengths = callInputs ? callInputs.map((inputArray) => inputArray.length) : []
const unformatedResults = callResults.map((result) =>
toCallState(result, contractInterface, fragment, latestBlockNumber)
)
return callInputLengths.map((length) => {
let j = 0
const indexElements: any = []
while (j < length) {
indexElements.push(unformatedResults.shift())
j++
}
return indexElements
})
}, [fragment, callInputs, callResults, contractInterface, latestBlockNumber])
}
export function useSingleCallResult( export function useSingleCallResult(
contract: Contract | null | undefined, contract: Contract | null | undefined,
methodName: string, methodName: string,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment