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 />
......
import { TransactionResponse } from '@ethersproject/providers'
import { abi as GOV_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
import { CurrencyAmount, Token } from '@uniswap/sdk-core' 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 { 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 { UNI } from '../../constants/tokens'
import { useGovernanceContracts, useUniContract } from '../../hooks/useContract' import { useMultipleContractMultipleData, useMultipleContractSingleData, useSingleCallResult } from '../multicall/hooks'
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 { useTransactionAdder } from '../transactions/hooks'
import { useState, useEffect, useCallback, useMemo } from 'react'
import { abi as GOV_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
import { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from 'constants/proposals/uniswap_grants_proposal_description'
interface ProposalDetail { interface ProposalDetail {
target: string target: string
...@@ -43,52 +45,72 @@ export enum ProposalState { ...@@ -43,52 +45,72 @@ export enum ProposalState {
Executed, Executed,
} }
// get count of all proposals made on the given governor alpha const GovernanceInterface = new ethers.utils.Interface(GOV_ABI)
function useProposalCount(govContract: ethers.Contract | null): number | undefined { // get count of all proposals made
const res = useSingleCallResult(govContract, 'proposalCount') export function useProposalCounts(): Record<string, number> | undefined {
if (res.result && !res.loading) { const { chainId } = useActiveWeb3React()
return parseInt(res.result[0]) const addresses = useMemo(() => {
} if (!chainId) {
return undefined 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 * Need proposal events to get description data emitted from
* new proposal event. * new proposal event.
*/ */
const eventParser = new ethers.utils.Interface(GOV_ABI) export function useDataFromEventLogs() {
function useDataFromEventLogs(govContract: ethers.Contract | null) {
const { library, chainId } = useActiveWeb3React() const { library, chainId } = useActiveWeb3React()
const [formattedEvents, setFormattedEvents] = 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 filter for these specific events
const filter = useMemo( const filters = useMemo(
() => () =>
govContract ? { ...govContract.filters.ProposalCreated(), fromBlock: 10861678, toBlock: 'latest' } : undefined, govContracts
[govContract] ? govContracts.map((contract) => ({
...contract.filters.ProposalCreated(),
fromBlock: 10861678,
toBlock: 'latest',
}))
: undefined,
[govContracts]
) )
useEffect(() => { useEffect(() => {
if (!filter || !library) return if (!filters || !library) return
let stale = false let stale = false
if (!formattedEvents) { if (!formattedEvents) {
library const filterRequests = filters.map((filter) => library.getLogs(filter))
.getLogs(filter) Promise.all(filterRequests)
.then((proposalEvents) => { .then((events) => events.flat())
.then((governanceContractsProposalEvents) => {
if (stale) return if (stale) return
const formattedEventData = proposalEvents?.map((event) => { const formattedEventData = governanceContractsProposalEvents.map((event) => {
const eventParsed = eventParser.parseLog(event).args const eventParsed = GovernanceInterface.parseLog(event).args
return { return {
description: eventParsed.description, description: eventParsed.description,
details: eventParsed.targets.map((target: string, i: number) => { details: eventParsed.targets.map((target: string, i: number) => {
const signature = eventParsed.signatures[i] const signature = eventParsed.signatures[i]
const [name, types] = signature.substr(0, signature.length - 1).split('(') const [name, types] = signature.substr(0, signature.length - 1).split('(')
const calldata = eventParsed.calldatas[i] const calldata = eventParsed.calldatas[i]
const decoded = utils.defaultAbiCoder.decode(types.split(','), calldata) const decoded = utils.defaultAbiCoder.decode(types.split(','), calldata)
return { return {
target, target,
functionSig: name, functionSig: name,
...@@ -108,99 +130,99 @@ function useDataFromEventLogs(govContract: ethers.Contract | null) { ...@@ -108,99 +130,99 @@ function useDataFromEventLogs(govContract: ethers.Contract | null) {
} }
return return
}, [filter, library, formattedEvents, chainId]) }, [filters, library, formattedEvents, chainId])
return formattedEvents return formattedEvents
} }
// get data for all past and active proposals // get data for all past and active proposals
export function useAllProposalData(): ProposalData[][] { export function useAllProposalData() {
// fetch all governance contracts const { chainId } = useActiveWeb3React()
const govContracts = useGovernanceContracts() const proposalCounts = useProposalCounts()
// fetch the proposal count on the active contract const proposalIndexes = useMemo(() => {
const proposalCount = useProposalCount(govContracts[govContracts.length - 1]) 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 addresses = useMemo(() => {
if (!chainId) {
return []
}
return GOVERNANCE_ADDRESSES.map((addressMap) => addressMap[chainId]).filter(
(address) => proposalCounts && proposalCounts[address] > 0
)
}, [chainId, proposalCounts])
// get all proposals for all contracts // get metadata from past events
const proposalsIndicesByGovContract = [ const formattedEvents = useDataFromEventLogs()
[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 // get all proposal entities
const allProposalsByGovContract = [ const allProposalsCallData = useMultipleContractMultipleData(
useSingleContractMultipleData( addresses,
govContracts[0], GovernanceInterface,
'proposals', 'proposals',
proposalsIndicesByGovContract[0].map((i) => [i]) proposalIndexes
), ).flat()
useSingleContractMultipleData(
govContracts[1],
'proposals',
proposalsIndicesByGovContract[1].map((i) => [i])
),
]
// get all proposal states // get all proposal states
const allProposalStatesByGovContract = [ const allProposalStatesCallData = useMultipleContractMultipleData(
useSingleContractMultipleData( addresses,
govContracts[0], GovernanceInterface,
'state', 'state',
proposalsIndicesByGovContract[0].map((i) => [i]) proposalIndexes
), ).flat()
useSingleContractMultipleData(
govContracts[1], if (
'state', !allProposalsCallData?.every((p) => Boolean(p.result)) ||
proposalsIndicesByGovContract[1].map((i) => [i]) !allProposalStatesCallData?.every((p) => Boolean(p.result)) ||
), !formattedEvents?.every((p) => Boolean(p))
] ) {
return []
// get metadata from past events
const formattedEventsByGovContract = [useDataFromEventLogs(govContracts[0]), useDataFromEventLogs(govContracts[1])]
const returnData: ProposalData[][] = []
for (let governorIndex = 0; governorIndex < allProposalsByGovContract.length; governorIndex++) {
const allProposals = allProposalsByGovContract[governorIndex]
const allProposalStates = allProposalStatesByGovContract[governorIndex]
const formattedEvents = formattedEventsByGovContract[governorIndex]
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([])
}
} }
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 { export function useProposalData(id: string): ProposalData | undefined {
// TODO don't hardcode for first gov alpha const allProposalData = useAllProposalData()
const allProposalData = useAllProposalData()[0]
return allProposalData?.find((p) => p.id === id) return allProposalData?.find((p) => p.id === id)
} }
...@@ -266,18 +288,16 @@ export function useVoteCallback(): { ...@@ -266,18 +288,16 @@ export function useVoteCallback(): {
} { } {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
// we only care about voting on the active governance contract
const govContracts = useGovernanceContracts() const govContracts = useGovernanceContracts()
const govContract = govContracts[govContracts.length - 1] const latestGovernanceContract = govContracts ? govContracts[0] : null
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const voteCallback = useCallback( const voteCallback = useCallback(
(proposalId: string | undefined, support: boolean) => { (proposalId: string | undefined, support: boolean) => {
if (!account || !govContract || !proposalId) return if (!account || !latestGovernanceContract || !proposalId) return
const args = [proposalId, support] const args = [proposalId, support]
return govContract.estimateGas.castVote(...args, {}).then((estimatedGasLimit) => { return latestGovernanceContract.estimateGas.castVote(...args, {}).then((estimatedGasLimit) => {
return govContract return latestGovernanceContract
.castVote(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) }) .castVote(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) })
.then((response: TransactionResponse) => { .then((response: TransactionResponse) => {
addTransaction(response, { addTransaction(response, {
...@@ -287,7 +307,7 @@ export function useVoteCallback(): { ...@@ -287,7 +307,7 @@ export function useVoteCallback(): {
}) })
}) })
}, },
[account, addTransaction, govContract] [account, addTransaction, latestGovernanceContract]
) )
return { voteCallback } return { voteCallback }
} }
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