Commit 30cffb74 authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

feat: governor bravo support (#2271)

* first pass

* Fix code style issues with ESLint

* address comments
Co-authored-by: default avatarLint Action <lint-action@samuelmeuli.com>
parent f59c5f68
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldAdmin",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newAdmin",
"type": "address"
}
],
"name": "NewAdmin",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldImplementation",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newImplementation",
"type": "address"
}
],
"name": "NewImplementation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldPendingAdmin",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newPendingAdmin",
"type": "address"
}
],
"name": "NewPendingAdmin",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "ProposalCanceled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "proposer",
"type": "address"
},
{
"indexed": false,
"internalType": "address[]",
"name": "targets",
"type": "address[]"
},
{
"indexed": false,
"internalType": "uint256[]",
"name": "values",
"type": "uint256[]"
},
{
"indexed": false,
"internalType": "string[]",
"name": "signatures",
"type": "string[]"
},
{
"indexed": false,
"internalType": "bytes[]",
"name": "calldatas",
"type": "bytes[]"
},
{
"indexed": false,
"internalType": "uint256",
"name": "startBlock",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "endBlock",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "description",
"type": "string"
}
],
"name": "ProposalCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "ProposalExecuted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "eta",
"type": "uint256"
}
],
"name": "ProposalQueued",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "oldProposalThreshold",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newProposalThreshold",
"type": "uint256"
}
],
"name": "ProposalThresholdSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "voter",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint8",
"name": "support",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint256",
"name": "votes",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "reason",
"type": "string"
}
],
"name": "VoteCast",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "oldVotingDelay",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newVotingDelay",
"type": "uint256"
}
],
"name": "VotingDelaySet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "oldVotingPeriod",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newVotingPeriod",
"type": "uint256"
}
],
"name": "VotingPeriodSet",
"type": "event"
},
{
"constant": true,
"inputs": [],
"name": "BALLOT_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "DOMAIN_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MAX_PROPOSAL_THRESHOLD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MAX_VOTING_DELAY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MAX_VOTING_PERIOD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MIN_PROPOSAL_THRESHOLD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MIN_VOTING_DELAY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MIN_VOTING_PERIOD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "_acceptAdmin",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "proposalCount",
"type": "uint256"
}
],
"name": "_initiate",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "newPendingAdmin",
"type": "address"
}
],
"name": "_setPendingAdmin",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "newProposalThreshold",
"type": "uint256"
}
],
"name": "_setProposalThreshold",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "newVotingDelay",
"type": "uint256"
}
],
"name": "_setVotingDelay",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "newVotingPeriod",
"type": "uint256"
}
],
"name": "_setVotingPeriod",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "admin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
}
],
"name": "cancel",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "support",
"type": "uint8"
}
],
"name": "castVote",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "support",
"type": "uint8"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "castVoteBySig",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "support",
"type": "uint8"
},
{
"internalType": "string",
"name": "reason",
"type": "string"
}
],
"name": "castVoteWithReason",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
}
],
"name": "execute",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
}
],
"name": "getActions",
"outputs": [
{
"internalType": "address[]",
"name": "targets",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "values",
"type": "uint256[]"
},
{
"internalType": "string[]",
"name": "signatures",
"type": "string[]"
},
{
"internalType": "bytes[]",
"name": "calldatas",
"type": "bytes[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
},
{
"internalType": "address",
"name": "voter",
"type": "address"
}
],
"name": "getReceipt",
"outputs": [
{
"components": [
{
"internalType": "bool",
"name": "hasVoted",
"type": "bool"
},
{
"internalType": "uint8",
"name": "support",
"type": "uint8"
},
{
"internalType": "uint96",
"name": "votes",
"type": "uint96"
}
],
"internalType": "struct GovernorBravoDelegateStorageV1.Receipt",
"name": "",
"type": "tuple"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "implementation",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "initialProposalId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "timelock_",
"type": "address"
},
{
"internalType": "address",
"name": "uni_",
"type": "address"
},
{
"internalType": "uint256",
"name": "votingPeriod_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "votingDelay_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "proposalThreshold_",
"type": "uint256"
}
],
"name": "initialize",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "latestProposalIds",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "pendingAdmin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "proposalCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "proposalMaxOperations",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "proposalThreshold",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposals",
"outputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "address",
"name": "proposer",
"type": "address"
},
{
"internalType": "uint256",
"name": "eta",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "startBlock",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "endBlock",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "forVotes",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "againstVotes",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "abstainVotes",
"type": "uint256"
},
{
"internalType": "bool",
"name": "canceled",
"type": "bool"
},
{
"internalType": "bool",
"name": "executed",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address[]",
"name": "targets",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "values",
"type": "uint256[]"
},
{
"internalType": "string[]",
"name": "signatures",
"type": "string[]"
},
{
"internalType": "bytes[]",
"name": "calldatas",
"type": "bytes[]"
},
{
"internalType": "string",
"name": "description",
"type": "string"
}
],
"name": "propose",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
}
],
"name": "queue",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "quorumVotes",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "proposalId",
"type": "uint256"
}
],
"name": "state",
"outputs": [
{
"internalType": "enum GovernorBravoDelegateStorageV1.ProposalState",
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "timelock",
"outputs": [
{
"internalType": "contract TimelockInterface",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "uni",
"outputs": [
{
"internalType": "contract UniInterface",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "votingDelay",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "votingPeriod",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
......@@ -10,7 +10,7 @@ import { TYPE, CustomLightSpinner } from '../../theme'
import { X, ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
import Circle from '../../assets/images/blue-loader.svg'
import { useVoteCallback, useUserVotes } from '../../state/governance/hooks'
import { useVoteCallback, useUserVotes, VoteOption } from '../../state/governance/hooks'
import { ExternalLink } from '../../theme/components'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { Trans } from '@lingui/macro'
......@@ -38,17 +38,13 @@ const ConfirmedIcon = styled(ColumnCenter)`
interface VoteModalProps {
isOpen: boolean
onDismiss: () => void
support: boolean // if user is for or against proposal
voteOption: VoteOption | undefined
proposalId: string | undefined // id for the proposal to vote on
}
export default function VoteModal({ isOpen, onDismiss, proposalId, support }: VoteModalProps) {
export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }: VoteModalProps) {
const { chainId } = useActiveWeb3React()
const {
voteCallback,
}: {
voteCallback: (proposalId: string | undefined, support: boolean) => Promise<string> | undefined
} = useVoteCallback()
const { voteCallback } = useVoteCallback()
const { votes: availableVotes } = useUserVotes()
// monitor call to help UI loading state
......@@ -69,10 +65,10 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, support }: Vo
setAttempting(true)
// if callback not returned properly ignore
if (!voteCallback) return
if (!voteCallback || voteOption === undefined) return
// try delegation and store hash
const hash = await voteCallback(proposalId, support)?.catch((error) => {
const hash = await voteCallback(proposalId, voteOption)?.catch((error) => {
setAttempting(false)
console.log(error)
})
......@@ -89,10 +85,12 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, support }: Vo
<AutoColumn gap="lg" justify="center">
<RowBetween>
<TYPE.mediumHeader fontWeight={500}>
{support ? (
{voteOption === VoteOption.Against ? (
<Trans>Vote against proposal {proposalId}</Trans>
) : voteOption === VoteOption.For ? (
<Trans>Vote for proposal {proposalId}</Trans>
) : (
<Trans>Vote against proposal {proposalId}</Trans>
<Trans>Vote to abstain on proposal {proposalId}</Trans>
)}
</TYPE.mediumHeader>
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
......@@ -102,10 +100,12 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, support }: Vo
</TYPE.largeHeader>
<ButtonPrimary onClick={onVote}>
<TYPE.mediumHeader color="white">
{support ? (
{voteOption === VoteOption.Against ? (
<Trans>Vote against proposal {proposalId}</Trans>
) : voteOption === VoteOption.For ? (
<Trans>Vote for proposal {proposalId}</Trans>
) : (
<Trans>Vote against proposal {proposalId}</Trans>
<Trans>Vote to abstain on proposal {proposalId}</Trans>
)}
</TYPE.mediumHeader>
</ButtonPrimary>
......
......@@ -16,17 +16,23 @@ export const V2_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V2_FACTO
export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D')
/**
* The older V0 governance account
* The oldest V0 governance address
*/
export const GOVERNANCE_ALPHA_V0_ADDRESSES: AddressMap = constructSameAddressMap(
'0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F'
)
/**
* The latest governor alpha that is currently admin of timelock
* The older V1 governance address
*/
export const GOVERNANCE_ALPHA_V1_ADDRESSES: AddressMap = {
[SupportedChainId.MAINNET]: '0xC4e172459f1E7939D522503B81AFAaC1014CE6F6',
}
/**
* The latest governor bravo that is currently admin of timelock
*/
export const GOVERNANCE_BRAVO_ADDRESSES: AddressMap = {
[SupportedChainId.MAINNET]: '0x408ED6354d4973f66138C91495F2f2FCbd8724C3',
}
export const TIMELOCK_ADDRESS: AddressMap = constructSameAddressMap('0x1a9C8182C09F50C8318d769245beA52c32BE35BC')
......
......@@ -10,6 +10,7 @@ import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUnisw
import { abi as MulticallABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
import ARGENT_WALLET_DETECTOR_ABI from 'abis/argent-wallet-detector.json'
import GOVERNOR_BRAVO_ABI from 'abis/governor-bravo.json'
import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json'
import ENS_ABI from 'abis/ens-registrar.json'
import ERC20_ABI from 'abis/erc20.json'
......@@ -28,6 +29,7 @@ import {
ENS_REGISTRAR_ADDRESSES,
GOVERNANCE_ALPHA_V0_ADDRESSES,
GOVERNANCE_ALPHA_V1_ADDRESSES,
GOVERNANCE_BRAVO_ADDRESSES,
} from 'constants/addresses'
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { useMemo } from 'react'
......@@ -111,14 +113,18 @@ export function useMerkleDistributorContract() {
}
export function useGovernanceV0Contract(): Contract | null {
return useContract(GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ABI, true)
return useContract(GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ABI, false)
}
export function useGovernanceV1Contract(): Contract | null {
return useContract(GOVERNANCE_ALPHA_V1_ADDRESSES, GOVERNANCE_ABI, true)
return useContract(GOVERNANCE_ALPHA_V1_ADDRESSES, GOVERNANCE_ABI, false)
}
export const useLatestGovernanceContract = useGovernanceV1Contract
export function useGovernanceBravoContract(): Contract | null {
return useContract(GOVERNANCE_BRAVO_ADDRESSES, GOVERNOR_BRAVO_ABI, true)
}
export const useLatestGovernanceContract = useGovernanceBravoContract
export function useUniContract() {
const { chainId } = useActiveWeb3React()
......
......@@ -87,7 +87,8 @@ export default function CreateProposal() {
const { account, chainId } = useActiveWeb3React()
const latestProposalId = useLatestProposalId(account ?? undefined) ?? '0'
const latestProposalData = useProposalData(0, latestProposalId)
// the first argument below should be the index of the latest governor
const latestProposalData = useProposalData(/* governorIndex */ 2, latestProposalId)
const { votes: availableVotes } = useUserVotes()
const proposalThreshold: CurrencyAmount<Token> | undefined = useProposalThreshold()
......
......@@ -33,6 +33,7 @@ import {
useProposalData,
useUserDelegatee,
useUserVotesAsOfBlock,
VoteOption,
} from '../../state/governance/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, TYPE } from '../../theme'
......@@ -130,8 +131,8 @@ export default function VotePage({
// get data for this specific proposal
const proposalData: ProposalData | undefined = useProposalData(Number.parseInt(governorIndex), id)
// update support based on button interactions
const [support, setSupport] = useState<boolean>(true)
// update vote option based on button interactions
const [voteOption, setVoteOption] = useState<VoteOption | undefined>(undefined)
// modal for casting votes
const showVoteModal = useModalOpen(ApplicationModal.VOTE)
......@@ -203,7 +204,12 @@ export default function VotePage({
return (
<>
<PageWrapper gap="lg" justify="center">
<VoteModal isOpen={showVoteModal} onDismiss={toggleVoteModal} proposalId={proposalData?.id} support={support} />
<VoteModal
isOpen={showVoteModal}
onDismiss={toggleVoteModal}
proposalId={proposalData?.id}
voteOption={voteOption}
/>
<DelegateModal isOpen={showDelegateModal} onDismiss={toggleDelegateModal} title={<Trans>Unlock Votes</Trans>} />
<ProposalInfo gap="lg" justify="start">
<RowBetween style={{ width: '100%' }}>
......@@ -252,7 +258,7 @@ export default function VotePage({
padding="8px"
$borderRadius="8px"
onClick={() => {
setSupport(true)
setVoteOption(VoteOption.For)
toggleVoteModal()
}}
>
......@@ -262,7 +268,7 @@ export default function VotePage({
padding="8px"
$borderRadius="8px"
onClick={() => {
setSupport(false)
setVoteOption(VoteOption.Against)
toggleVoteModal()
}}
>
......
......@@ -14,6 +14,7 @@ import {
Utf8ErrorReason,
} from 'ethers/lib/utils'
import {
useGovernanceBravoContract,
useGovernanceV0Contract,
useGovernanceV1Contract,
useLatestGovernanceContract,
......@@ -86,71 +87,80 @@ interface FormattedProposalLog {
* Need proposal events to get description data emitted from
* new proposal event.
*/
function useFormattedProposalCreatedLogs(contract: Contract | null): FormattedProposalLog[] | undefined {
function useFormattedProposalCreatedLogs(
contract: Contract | null,
indices: number[][]
): FormattedProposalLog[] | undefined {
// create filters for ProposalCreated events
const filter = useMemo(() => contract?.filters?.ProposalCreated(), [contract])
const useLogsResult = useLogs(filter)
return useMemo(() => {
return useLogsResult?.logs?.map((log) => {
const parsed = GovernanceInterface.parseLog(log).args
let description!: string
try {
description = parsed.description
} catch (error) {
// replace invalid UTF-8 in the description with replacement characters
let onError = Utf8ErrorFuncs.replace
// Bravo proposal reverses the codepoints for U+2018 (‘) and U+2026 (…)
const startBlock = parseInt(parsed.startBlock?.toString())
if (startBlock === BRAVO_START_BLOCK) {
const U2018 = [0xe2, 0x80, 0x98].toString()
const U2026 = [0xe2, 0x80, 0xa6].toString()
onError = (reason, offset, bytes, output) => {
if (reason === Utf8ErrorReason.UNEXPECTED_CONTINUE) {
const charCode = [bytes[offset], bytes[offset + 1], bytes[offset + 2]].reverse().toString()
if (charCode === U2018) {
output.push(0x2018)
return 2
} else if (charCode === U2026) {
output.push(0x2026)
return 2
return useLogsResult?.logs
?.map((log) => {
const parsed = GovernanceInterface.parseLog(log).args
return parsed
})
?.filter((parsed) => indices.flat().some((i) => i === parsed.id.toNumber()))
?.map((parsed) => {
let description!: string
try {
description = parsed.description
} catch (error) {
// replace invalid UTF-8 in the description with replacement characters
let onError = Utf8ErrorFuncs.replace
// Bravo proposal reverses the codepoints for U+2018 (‘) and U+2026 (…)
const startBlock = parseInt(parsed.startBlock?.toString())
if (startBlock === BRAVO_START_BLOCK) {
const U2018 = [0xe2, 0x80, 0x98].toString()
const U2026 = [0xe2, 0x80, 0xa6].toString()
onError = (reason, offset, bytes, output) => {
if (reason === Utf8ErrorReason.UNEXPECTED_CONTINUE) {
const charCode = [bytes[offset], bytes[offset + 1], bytes[offset + 2]].reverse().toString()
if (charCode === U2018) {
output.push(0x2018)
return 2
} else if (charCode === U2026) {
output.push(0x2026)
return 2
}
}
return Utf8ErrorFuncs.replace(reason, offset, bytes, output)
}
return Utf8ErrorFuncs.replace(reason, offset, bytes, output)
}
}
description = JSON.parse(toUtf8String(error.error.value, onError)) || ''
description = JSON.parse(toUtf8String(error.error.value, onError)) || ''
// Bravo proposal omits newlines
if (startBlock === BRAVO_START_BLOCK) {
description = description.replaceAll(/ /g, '\n').replaceAll(/\d\. /g, '\n$&')
}
}
return {
description,
details: parsed.targets.map((target: string, i: number) => {
const signature = parsed.signatures[i]
const [name, types] = signature.substr(0, signature.length - 1).split('(')
const calldata = parsed.calldatas[i]
const decoded = defaultAbiCoder.decode(types.split(','), calldata)
return {
target,
functionSig: name,
callData: decoded.join(', '),
// Bravo proposal omits newlines
if (startBlock === BRAVO_START_BLOCK) {
description = description.replaceAll(/ /g, '\n').replaceAll(/\d\. /g, '\n$&')
}
}),
}
})
}, [useLogsResult])
}
return {
description,
details: parsed.targets.map((target: string, i: number) => {
const signature = parsed.signatures[i]
const [name, types] = signature.substr(0, signature.length - 1).split('(')
const calldata = parsed.calldatas[i]
const decoded = defaultAbiCoder.decode(types.split(','), calldata)
return {
target,
functionSig: name,
callData: decoded.join(', '),
}
}),
}
})
}, [indices, useLogsResult])
}
const V0_PROPOSAL_IDS = [[1], [2], [3], [4]]
const V1_PROPOSAL_IDS = [[1], [2], [3]]
function countToIndices(count: number | undefined) {
return typeof count === 'number' ? new Array(count).fill(0).map((_, i) => [i + 1]) : []
function countToIndices(count: number | undefined, skip = 0) {
return typeof count === 'number' ? new Array(count - skip).fill(0).map((_, i) => [i + 1 + skip]) : []
}
// get data for all past and active proposals
......@@ -158,41 +168,48 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean }
const { chainId } = useActiveWeb3React()
const gov0 = useGovernanceV0Contract()
const gov1 = useGovernanceV1Contract()
const gov2 = useGovernanceBravoContract()
const proposalCount0 = useProposalCount(gov0)
const proposalCount1 = useProposalCount(gov1)
const proposalCount2 = useProposalCount(gov2)
const gov0ProposalIndexes = useMemo(() => {
return chainId === SupportedChainId.MAINNET ? V0_PROPOSAL_IDS : countToIndices(proposalCount0)
}, [chainId, proposalCount0])
const gov1ProposalIndexes = useMemo(() => {
return countToIndices(proposalCount1)
}, [proposalCount1])
return chainId === SupportedChainId.MAINNET ? V1_PROPOSAL_IDS : countToIndices(proposalCount1)
}, [chainId, proposalCount1])
const gov2ProposalIndexes = useMemo(() => {
return countToIndices(proposalCount2, 8)
}, [proposalCount2])
const proposalsV0 = useSingleContractMultipleData(gov0, 'proposals', gov0ProposalIndexes)
const proposalsV1 = useSingleContractMultipleData(gov1, 'proposals', gov1ProposalIndexes)
const proposalsV2 = useSingleContractMultipleData(gov2, 'proposals', gov2ProposalIndexes)
// get all proposal states
const proposalStatesV0 = useSingleContractMultipleData(gov0, 'state', gov0ProposalIndexes)
const proposalStatesV1 = useSingleContractMultipleData(gov1, 'state', gov1ProposalIndexes)
const proposalStatesV2 = useSingleContractMultipleData(gov2, 'state', gov2ProposalIndexes)
// get metadata from past events
const formattedLogsV0 = useFormattedProposalCreatedLogs(gov0)
const formattedLogsV1 = useFormattedProposalCreatedLogs(gov1)
const formattedLogsV0 = useFormattedProposalCreatedLogs(gov0, gov0ProposalIndexes)
const formattedLogsV1 = useFormattedProposalCreatedLogs(gov1, gov1ProposalIndexes)
const formattedLogsV2 = useFormattedProposalCreatedLogs(gov2, gov2ProposalIndexes)
// early return until events are fetched
return useMemo(() => {
const proposalsCallData = proposalsV0.concat(proposalsV1)
const proposalStatesCallData = proposalStatesV0.concat(proposalStatesV1)
const formattedLogs = (formattedLogsV0 ?? [])
.slice(0, proposalsV0.length)
.concat((formattedLogsV1 ?? []).slice(0, proposalsV1.length))
const proposalsCallData = [...proposalsV0, ...proposalsV1, ...proposalsV2]
const proposalStatesCallData = [...proposalStatesV0, ...proposalStatesV1, ...proposalStatesV2]
const formattedLogs = [...(formattedLogsV0 ?? []), ...(formattedLogsV1 ?? []), ...(formattedLogsV2 ?? [])]
if (
proposalsCallData.some((p) => p.loading) ||
proposalStatesCallData.some((p) => p.loading) ||
(gov0 && !formattedLogsV0) ||
(gov1 && !formattedLogsV1)
(gov1 && !formattedLogsV1) ||
(gov2 && !formattedLogsV2)
) {
return { data: [], loading: true }
}
......@@ -212,10 +229,11 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean }
status: proposalStatesCallData[i]?.result?.[0] ?? ProposalState.UNDETERMINED,
forCount: parseFloat(formatUnits(proposal?.result?.forVotes?.toString() ?? 0, 18)),
againstCount: parseFloat(formatUnits(proposal?.result?.againstVotes?.toString() ?? 0, 18)),
abstainCount: parseFloat(formatUnits(proposal?.result?.abstainVotes?.toString() ?? 0, 18)),
startBlock,
endBlock: parseInt(proposal?.result?.endBlock?.toString()),
details: formattedLogs[i]?.details,
governorIndex: i >= gov0ProposalIndexes.length ? 1 : 0,
governorIndex: i >= proposalsV0.length + proposalsV1.length ? 2 : i >= proposalsV0.length ? 1 : 0,
}
}),
loading: false,
......@@ -223,13 +241,16 @@ export function useAllProposalData(): { data: ProposalData[]; loading: boolean }
}, [
formattedLogsV0,
formattedLogsV1,
formattedLogsV2,
gov0,
gov0ProposalIndexes.length,
gov1,
gov2,
proposalStatesV0,
proposalStatesV1,
proposalStatesV2,
proposalsV0,
proposalsV1,
proposalsV2,
])
}
......@@ -297,8 +318,14 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi
)
}
export enum VoteOption {
Against,
For,
Abstain,
}
export function useVoteCallback(): {
voteCallback: (proposalId: string | undefined, support: boolean) => undefined | Promise<string>
voteCallback: (proposalId: string | undefined, voteOption: VoteOption) => undefined | Promise<string>
} {
const { account, chainId } = useActiveWeb3React()
......@@ -307,15 +334,17 @@ export function useVoteCallback(): {
const addTransaction = useTransactionAdder()
const voteCallback = useCallback(
(proposalId: string | undefined, support: boolean) => {
(proposalId: string | undefined, voteOption: VoteOption) => {
if (!account || !latestGovernanceContract || !proposalId || !chainId) return
const args = [proposalId, support]
const args = [proposalId, voteOption === VoteOption.Against ? 0 : voteOption === VoteOption.For ? 1 : 2]
return latestGovernanceContract.estimateGas.castVote(...args, {}).then((estimatedGasLimit) => {
return latestGovernanceContract
.castVote(...args, { value: null, gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Voted ${support ? 'for ' : 'against'} proposal ${proposalId}`,
summary: `Voted ${
voteOption === VoteOption.Against ? 'against' : voteOption === VoteOption.For ? 'for' : 'to abstain on'
} proposal ${proposalId}`,
})
return response.hash
})
......@@ -362,9 +391,8 @@ export function useCreateProposalCallback(): (
}
export function useLatestProposalId(address: string | undefined): string | undefined {
const govContractV1 = useGovernanceV1Contract()
const res = useSingleCallResult(govContractV1, 'latestProposalIds', [address])
const latestGovernanceContract = useLatestGovernanceContract()
const res = useSingleCallResult(latestGovernanceContract, 'latestProposalIds', [address])
return res?.result?.[0]?.toString()
}
......
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