feat: add multi-contract governance support (#1860)
* Revert "feat: quick fix for new governor" This reverts commit 5dd1249dddc3e097c26a68bd5b4157ffa341dd60. * support multiple governance contracts
This commit is contained in:
parent
1b27d8dab0
commit
014595cdfb
@ -1,33 +1,29 @@
|
||||
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 { Trans } from '@lingui/macro'
|
||||
import useScrollPosition from '@react-hook/window-scroll'
|
||||
import { darken } from 'polished'
|
||||
import React, { useState } from 'react'
|
||||
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 Logo from '../../assets/svg/logo.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 { useDarkModeManager } from '../../state/user/hooks'
|
||||
import { useETHBalances } from '../../state/wallet/hooks'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import { TYPE, ExternalLink } from '../../theme'
|
||||
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
import { YellowCard } from '../Card'
|
||||
import Menu from '../Menu'
|
||||
|
||||
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 { CardNoise } from '../earn/styled'
|
||||
import Menu from '../Menu'
|
||||
import Modal from '../Modal'
|
||||
import Row, { RowFixed } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import Web3Status from '../Web3Status'
|
||||
import UniBalanceContent from './UniBalanceContent'
|
||||
|
||||
const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
@ -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() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
|
@ -16,11 +16,13 @@ export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap(
|
||||
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
|
||||
false
|
||||
)
|
||||
|
||||
// most current governance contract address should always be the 0 index
|
||||
export const GOVERNANCE_ADDRESSES: AddressMap[] = [
|
||||
constructSameAddressMap('0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F', false),
|
||||
{
|
||||
[SupportedChainId.MAINNET]: '0xC4e172459f1E7939D522503B81AFAaC1014CE6F6',
|
||||
},
|
||||
constructSameAddressMap('0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F', false),
|
||||
]
|
||||
export const TIMELOCK_ADDRESS: AddressMap = constructSameAddressMap('0x1a9C8182C09F50C8318d769245beA52c32BE35BC', false)
|
||||
export const MERKLE_DISTRIBUTOR_ADDRESS: AddressMap = {
|
||||
|
@ -7,3 +7,13 @@ export enum SupportedChainId {
|
||||
ARBITRUM_KOVAN = 144545313136048,
|
||||
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',
|
||||
}
|
||||
|
@ -1,11 +1,21 @@
|
||||
import { GOVERNANCE_ADDRESSES, TIMELOCK_ADDRESS, UNI_ADDRESS } from './addresses'
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
export const COMMON_CONTRACT_NAMES: { [chainId: number]: { [address: string]: string } } = {
|
||||
[1]: {
|
||||
[UNI_ADDRESS[1]]: 'UNI',
|
||||
[GOVERNANCE_ADDRESSES[0][1]]: 'Governance (V0)',
|
||||
[GOVERNANCE_ADDRESSES[1][1]]: 'Governance',
|
||||
[TIMELOCK_ADDRESS[1]]: 'Timelock',
|
||||
// returns { [address]: `Governance (V${n})`} for each address in GOVERNANCE_ADDRESSES except the current, which gets no version indicator
|
||||
const governanceContracts = (): Record<string, string> =>
|
||||
GOVERNANCE_ADDRESSES.reduce(
|
||||
(acc, addressMap, i) => ({
|
||||
...acc,
|
||||
[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(),
|
||||
},
|
||||
}
|
||||
|
||||
|
2
src/constants/proposals/index.ts
Normal file
2
src/constants/proposals/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const UNISWAP_GRANTS_START_BLOCK = 11473815
|
||||
export const EDUCATION_FUND_1_START_BLOCK = 12620175
|
@ -46,19 +46,34 @@ import { useActiveWeb3React } from './web3'
|
||||
|
||||
// returns null on errors
|
||||
export function useContract<T extends Contract = Contract>(
|
||||
addressOrAddressMap: string | { [chainId: number]: string } | undefined,
|
||||
addressOrAddressMap: string | { [chainId: number]: 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 address = addressOrAddressMap[chainId]
|
||||
if (!address) return null
|
||||
if (typeof addressOrAddressMap === 'string') {
|
||||
address = addressOrAddressMap
|
||||
} else if (!Array.isArray(addressOrAddressMap)) {
|
||||
address = addressOrAddressMap[chainId]
|
||||
}
|
||||
if (!address && !Array.isArray(addressOrAddressMap)) {
|
||||
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)
|
||||
@ -116,11 +131,22 @@ export function useMerkleDistributorContract() {
|
||||
return useContract(MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ABI, true)
|
||||
}
|
||||
|
||||
export function useGovernanceContracts(): (Contract | null)[] {
|
||||
return [
|
||||
useContract(GOVERNANCE_ADDRESSES[0], GOVERNANCE_ABI, false),
|
||||
useContract(GOVERNANCE_ADDRESSES[1], GOVERNANCE_ABI, true),
|
||||
]
|
||||
export function useGovernanceContracts(): Contract[] | null {
|
||||
const { library, account, chainId } = useActiveWeb3React()
|
||||
|
||||
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() {
|
||||
|
@ -120,8 +120,7 @@ export default function Vote() {
|
||||
const toggleDelegateModal = useToggleDelegateModal()
|
||||
|
||||
// get data to list all proposals
|
||||
// TODO don't hardcode for first gov alpha
|
||||
const allProposals: ProposalData[] = useAllProposalData()[0]
|
||||
const allProposals: ProposalData[] = useAllProposalData()
|
||||
|
||||
// user data
|
||||
const availableVotes: CurrencyAmount<Token> | undefined = useUserVotes()
|
||||
@ -249,7 +248,7 @@ export default function Vote() {
|
||||
</TYPE.subHeader>
|
||||
</EmptyProposals>
|
||||
)}
|
||||
{allProposals?.reverse()?.map((p: ProposalData, i) => {
|
||||
{allProposals?.reverse().map((p: ProposalData, i) => {
|
||||
return (
|
||||
<Proposal as={Link} to={'/vote/' + p.id} key={i}>
|
||||
<ProposalNumber>{p.id}</ProposalNumber>
|
||||
@ -260,7 +259,7 @@ export default function Vote() {
|
||||
})}
|
||||
</TopSection>
|
||||
<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>
|
||||
</PageWrapper>
|
||||
<SwitchLocaleLink />
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { isAddress } from 'ethers/lib/utils'
|
||||
import { UNI } from '../../constants/tokens'
|
||||
import { useGovernanceContracts, useUniContract } from '../../hooks/useContract'
|
||||
import { calculateGasMargin } from '../../utils/calculateGasMargin'
|
||||
import { useSingleCallResult, useSingleContractMultipleData } from '../multicall/hooks'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { ethers, utils } from 'ethers'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { useTransactionAdder } from '../transactions/hooks'
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
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 { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from 'constants/proposals/uniswap_grants_proposal_description'
|
||||
import { 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 { UNI } from '../../constants/tokens'
|
||||
import { useMultipleContractMultipleData, useMultipleContractSingleData, useSingleCallResult } from '../multicall/hooks'
|
||||
import { useTransactionAdder } from '../transactions/hooks'
|
||||
|
||||
interface ProposalDetail {
|
||||
target: string
|
||||
@ -43,52 +45,72 @@ export enum ProposalState {
|
||||
Executed,
|
||||
}
|
||||
|
||||
// get count of all proposals made on the given governor alpha
|
||||
function useProposalCount(govContract: ethers.Contract | null): number | undefined {
|
||||
const res = useSingleCallResult(govContract, 'proposalCount')
|
||||
if (res.result && !res.loading) {
|
||||
return parseInt(res.result[0])
|
||||
}
|
||||
return undefined
|
||||
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 []
|
||||
}
|
||||
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])
|
||||
}
|
||||
|
||||
/**
|
||||
* Need proposal events to get description data emitted from
|
||||
* new proposal event.
|
||||
*/
|
||||
const eventParser = new ethers.utils.Interface(GOV_ABI)
|
||||
function useDataFromEventLogs(govContract: ethers.Contract | null) {
|
||||
export function useDataFromEventLogs() {
|
||||
const { library, chainId } = useActiveWeb3React()
|
||||
const [formattedEvents, setFormattedEvents] =
|
||||
useState<{ description: string; details: { target: string; functionSig: string; callData: string }[] }[]>()
|
||||
const govContracts = useGovernanceContracts()
|
||||
|
||||
// create filter for these specific events
|
||||
const filter = useMemo(
|
||||
const filters = useMemo(
|
||||
() =>
|
||||
govContract ? { ...govContract.filters.ProposalCreated(), fromBlock: 10861678, toBlock: 'latest' } : undefined,
|
||||
[govContract]
|
||||
govContracts
|
||||
? govContracts.map((contract) => ({
|
||||
...contract.filters.ProposalCreated(),
|
||||
fromBlock: 10861678,
|
||||
toBlock: 'latest',
|
||||
}))
|
||||
: undefined,
|
||||
[govContracts]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!filter || !library) return
|
||||
if (!filters || !library) return
|
||||
let stale = false
|
||||
|
||||
if (!formattedEvents) {
|
||||
library
|
||||
.getLogs(filter)
|
||||
.then((proposalEvents) => {
|
||||
const filterRequests = filters.map((filter) => library.getLogs(filter))
|
||||
Promise.all(filterRequests)
|
||||
.then((events) => events.flat())
|
||||
.then((governanceContractsProposalEvents) => {
|
||||
if (stale) return
|
||||
const formattedEventData = proposalEvents?.map((event) => {
|
||||
const eventParsed = eventParser.parseLog(event).args
|
||||
const formattedEventData = governanceContractsProposalEvents.map((event) => {
|
||||
const eventParsed = GovernanceInterface.parseLog(event).args
|
||||
return {
|
||||
description: eventParsed.description,
|
||||
details: eventParsed.targets.map((target: string, i: number) => {
|
||||
const signature = eventParsed.signatures[i]
|
||||
const [name, types] = signature.substr(0, signature.length - 1).split('(')
|
||||
|
||||
const calldata = eventParsed.calldatas[i]
|
||||
const decoded = utils.defaultAbiCoder.decode(types.split(','), calldata)
|
||||
|
||||
return {
|
||||
target,
|
||||
functionSig: name,
|
||||
@ -108,99 +130,99 @@ function useDataFromEventLogs(govContract: ethers.Contract | null) {
|
||||
}
|
||||
|
||||
return
|
||||
}, [filter, library, formattedEvents, chainId])
|
||||
}, [filters, library, formattedEvents, chainId])
|
||||
|
||||
return formattedEvents
|
||||
}
|
||||
|
||||
// get data for all past and active proposals
|
||||
export function useAllProposalData(): ProposalData[][] {
|
||||
// fetch all governance contracts
|
||||
const govContracts = useGovernanceContracts()
|
||||
export function useAllProposalData() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const proposalCounts = useProposalCounts()
|
||||
|
||||
// fetch the proposal count on the active contract
|
||||
const proposalCount = useProposalCount(govContracts[govContracts.length - 1])
|
||||
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])
|
||||
|
||||
// get all proposals for all contracts
|
||||
const proposalsIndicesByGovContract = [
|
||||
[1, 2, 3, 4], // hardcoded for first governor alpha
|
||||
typeof proposalCount === 'number' ? new Array(proposalCount).fill(0).map((_, i) => i + 1) : [], // dynamic for current governor alpha
|
||||
]
|
||||
|
||||
// get all proposal entities
|
||||
const allProposalsByGovContract = [
|
||||
useSingleContractMultipleData(
|
||||
govContracts[0],
|
||||
'proposals',
|
||||
proposalsIndicesByGovContract[0].map((i) => [i])
|
||||
),
|
||||
useSingleContractMultipleData(
|
||||
govContracts[1],
|
||||
'proposals',
|
||||
proposalsIndicesByGovContract[1].map((i) => [i])
|
||||
),
|
||||
]
|
||||
|
||||
// get all proposal states
|
||||
const allProposalStatesByGovContract = [
|
||||
useSingleContractMultipleData(
|
||||
govContracts[0],
|
||||
'state',
|
||||
proposalsIndicesByGovContract[0].map((i) => [i])
|
||||
),
|
||||
useSingleContractMultipleData(
|
||||
govContracts[1],
|
||||
'state',
|
||||
proposalsIndicesByGovContract[1].map((i) => [i])
|
||||
),
|
||||
]
|
||||
const addresses = useMemo(() => {
|
||||
if (!chainId) {
|
||||
return []
|
||||
}
|
||||
return GOVERNANCE_ADDRESSES.map((addressMap) => addressMap[chainId]).filter(
|
||||
(address) => proposalCounts && proposalCounts[address] > 0
|
||||
)
|
||||
}, [chainId, proposalCounts])
|
||||
|
||||
// get metadata from past events
|
||||
const formattedEventsByGovContract = [useDataFromEventLogs(govContracts[0]), useDataFromEventLogs(govContracts[1])]
|
||||
const formattedEvents = useDataFromEventLogs()
|
||||
|
||||
const returnData: ProposalData[][] = []
|
||||
// get all proposal entities
|
||||
const allProposalsCallData = useMultipleContractMultipleData(
|
||||
addresses,
|
||||
GovernanceInterface,
|
||||
'proposals',
|
||||
proposalIndexes
|
||||
).flat()
|
||||
|
||||
for (let governorIndex = 0; governorIndex < allProposalsByGovContract.length; governorIndex++) {
|
||||
const allProposals = allProposalsByGovContract[governorIndex]
|
||||
const allProposalStates = allProposalStatesByGovContract[governorIndex]
|
||||
const formattedEvents = formattedEventsByGovContract[governorIndex]
|
||||
// get all proposal states
|
||||
const allProposalStatesCallData = useMultipleContractMultipleData(
|
||||
addresses,
|
||||
GovernanceInterface,
|
||||
'state',
|
||||
proposalIndexes
|
||||
).flat()
|
||||
|
||||
if (
|
||||
allProposals?.every((p) => Boolean(p.result)) &&
|
||||
allProposalStates?.every((p) => Boolean(p.result)) &&
|
||||
formattedEvents?.every((p) => Boolean(p))
|
||||
) {
|
||||
returnData.push(
|
||||
allProposals.map((proposal, i): ProposalData => {
|
||||
let description = formattedEvents[i].description
|
||||
// overwrite broken description
|
||||
if (governorIndex === 0 && i === 2) description = UNISWAP_GRANTS_PROPOSAL_DESCRIPTION
|
||||
|
||||
return {
|
||||
id: proposal?.result?.id.toString(),
|
||||
title: description?.split(/# |\n/g)[1] ?? 'Untitled',
|
||||
description: description ?? 'No description.',
|
||||
proposer: proposal?.result?.proposer,
|
||||
status: allProposalStates[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: parseInt(proposal?.result?.startBlock?.toString()),
|
||||
endBlock: parseInt(proposal?.result?.endBlock?.toString()),
|
||||
details: formattedEvents[i].details,
|
||||
}
|
||||
})
|
||||
)
|
||||
} else {
|
||||
returnData.push([])
|
||||
}
|
||||
if (
|
||||
!allProposalsCallData?.every((p) => Boolean(p.result)) ||
|
||||
!allProposalStatesCallData?.every((p) => Boolean(p.result)) ||
|
||||
!formattedEvents?.every((p) => Boolean(p))
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
return returnData
|
||||
const omittedProposalStartBlocks = [EDUCATION_FUND_1_START_BLOCK]
|
||||
|
||||
return allProposalsCallData
|
||||
.map((proposal, i) => {
|
||||
let description = formattedEvents[i].description
|
||||
const startBlock = parseInt(proposal?.result?.startBlock?.toString())
|
||||
if (startBlock === UNISWAP_GRANTS_START_BLOCK) {
|
||||
description = UNISWAP_GRANTS_PROPOSAL_DESCRIPTION
|
||||
}
|
||||
return {
|
||||
id: proposal?.result?.id.toString(),
|
||||
title: description?.split(/# |\n/g)[1] ?? 'Untitled',
|
||||
description: description ?? 'No description.',
|
||||
proposer: proposal?.result?.proposer,
|
||||
status: allProposalStatesCallData[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,
|
||||
}
|
||||
})
|
||||
.filter((proposal) => !omittedProposalStartBlocks.includes(proposal.startBlock))
|
||||
}
|
||||
|
||||
export function useProposalData(id: string): ProposalData | undefined {
|
||||
// TODO don't hardcode for first gov alpha
|
||||
const allProposalData = useAllProposalData()[0]
|
||||
const allProposalData = useAllProposalData()
|
||||
return allProposalData?.find((p) => p.id === id)
|
||||
}
|
||||
|
||||
@ -266,18 +288,16 @@ export function useVoteCallback(): {
|
||||
} {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
// we only care about voting on the active governance contract
|
||||
const govContracts = useGovernanceContracts()
|
||||
const govContract = govContracts[govContracts.length - 1]
|
||||
|
||||
const latestGovernanceContract = govContracts ? govContracts[0] : null
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
const voteCallback = useCallback(
|
||||
(proposalId: string | undefined, support: boolean) => {
|
||||
if (!account || !govContract || !proposalId) return
|
||||
if (!account || !latestGovernanceContract || !proposalId) return
|
||||
const args = [proposalId, support]
|
||||
return govContract.estimateGas.castVote(...args, {}).then((estimatedGasLimit) => {
|
||||
return govContract
|
||||
return latestGovernanceContract.estimateGas.castVote(...args, {}).then((estimatedGasLimit) => {
|
||||
return latestGovernanceContract
|
||||
.castVote(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) })
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
@ -287,7 +307,7 @@ export function useVoteCallback(): {
|
||||
})
|
||||
})
|
||||
},
|
||||
[account, addTransaction, govContract]
|
||||
[account, addTransaction, latestGovernanceContract]
|
||||
)
|
||||
return { voteCallback }
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Interface, FunctionFragment } from '@ethersproject/abi'
|
||||
import { FunctionFragment, Interface } from '@ethersproject/abi'
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
@ -8,10 +8,10 @@ import { useBlockNumber } from '../application/hooks'
|
||||
import {
|
||||
addMulticallListeners,
|
||||
Call,
|
||||
removeMulticallListeners,
|
||||
parseCallKey,
|
||||
toCallKey,
|
||||
ListenerOptions,
|
||||
parseCallKey,
|
||||
removeMulticallListeners,
|
||||
toCallKey,
|
||||
} from './actions'
|
||||
|
||||
export interface Result extends ReadonlyArray<any> {
|
||||
@ -231,6 +231,63 @@ export function useMultipleContractSingleData(
|
||||
}, [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(
|
||||
contract: Contract | null | undefined,
|
||||
methodName: string,
|
||||
|
Loading…
Reference in New Issue
Block a user