From d71133524d23a4a1ec4f3b1af443ced6d0b655c8 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 16:31:11 -0400 Subject: [PATCH 01/26] Set "batch" requesting, can't hurt right lol --- src/providers/NetworkConfig/web3-modal.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/providers/NetworkConfig/web3-modal.config.ts b/src/providers/NetworkConfig/web3-modal.config.ts index afe0a48104..0678ca5f14 100644 --- a/src/providers/NetworkConfig/web3-modal.config.ts +++ b/src/providers/NetworkConfig/web3-modal.config.ts @@ -30,6 +30,9 @@ export const wagmiConfig = defaultWagmiConfig({ transports: { [mainnet.id]: http( `https://eth-mainnet.g.alchemy.com/v2/${import.meta.env.VITE_APP_ALCHEMY_MAINNET_API_KEY}`, + { + batch: true, + }, ), [sepolia.id]: http( `https://eth-sepolia.g.alchemy.com/v2/${import.meta.env.VITE_APP_ALCHEMY_SEPOLIA_API_KEY}`, From 951f58738091b9a7b74639a8428ab12bdc645eba Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 16:32:31 -0400 Subject: [PATCH 02/26] Make a new action and reducer for setting a single Azorius proposal into state --- src/providers/App/governance/action.ts | 5 +++++ src/providers/App/governance/reducer.ts | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/providers/App/governance/action.ts b/src/providers/App/governance/action.ts index 282c87bca7..0293593ea8 100644 --- a/src/providers/App/governance/action.ts +++ b/src/providers/App/governance/action.ts @@ -16,6 +16,7 @@ import { ProposalTemplate } from '../../../types/createProposalTemplate'; export enum FractalGovernanceAction { SET_GOVERNANCE_TYPE = 'SET_GOVERNANCE_TYPE', SET_PROPOSALS = 'SET_PROPOSALS', + SET_AZORIUS_PROPOSAL = 'SET_AZORIUS_PROPOSAL', SET_SNAPSHOT_PROPOSALS = 'SET_SNAPSHOT_PROPOSALS', SET_PROPOSAL_TEMPLATES = 'SET_PROPOSAL_TEMPLATES', SET_STRATEGY = 'SET_STRATEGY', @@ -60,6 +61,10 @@ export type FractalGovernanceActions = type: FractalGovernanceAction.SET_PROPOSALS; payload: FractalProposal[]; } + | { + type: FractalGovernanceAction.SET_AZORIUS_PROPOSAL; + payload: FractalProposal; + } | { type: FractalGovernanceAction.SET_SNAPSHOT_PROPOSALS; payload: FractalProposal[]; diff --git a/src/providers/App/governance/reducer.ts b/src/providers/App/governance/reducer.ts index 987772409f..6a3216f691 100644 --- a/src/providers/App/governance/reducer.ts +++ b/src/providers/App/governance/reducer.ts @@ -44,6 +44,12 @@ export const governanceReducer = (state: FractalGovernance, action: FractalGover ], }; } + case FractalGovernanceAction.SET_AZORIUS_PROPOSAL: { + return { + ...state, + proposals: [...(proposals || []), action.payload], + }; + } case FractalGovernanceAction.SET_PROPOSAL_TEMPLATES: { return { ...state, proposalTemplates: action.payload }; } From ccf7b6067c0fd3165baad627720c16b2d296d9bc Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 16:34:01 -0400 Subject: [PATCH 03/26] Unrelated to the rest of the work in here, but when getting Safe info about an address just make a single request, don't requestWithRetries --- src/hooks/DAO/loaders/useFractalNode.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index 293c9b2f2f..502c5f9111 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -102,11 +102,7 @@ export const useFractalNode = ( try { if (!safeAPI) throw new Error('SafeAPI not set'); - - safeInfo = await requestWithRetries( - () => safeAPI.getSafeInfo(utils.getAddress(_daoAddress)), - 5, - ); + safeInfo = await safeAPI.getSafeInfo(utils.getAddress(_daoAddress)); } catch (e) { reset({ error: true }); return; From 3f549b6083c3b2d54cd26cd2a309e0f41dd08d20 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 16:35:59 -0400 Subject: [PATCH 04/26] Comment out all of the Azorius Proposal Listeners --- .../loaders/governance/useAzoriusProposals.ts | 326 +++++++++--------- 1 file changed, 163 insertions(+), 163 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 127fa75646..f05e8166c4 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -116,184 +116,184 @@ export const useAzoriusProposals = () => { baseContracts, ]); - const { requestWithRetries } = useAsyncRetry(); - // Azrious proposals are listeners - const proposalCreatedListener: TypedListener = useCallback( - async (strategyAddress, proposalId, proposer, transactions, _metadata) => { - if ( - !azoriusContractAddress || - !(ozLinearVotingContractAddress || erc721LinearVotingContractAddress) || - !strategyType || - !provider || - !baseContracts - ) { - return; - } - let proposalData: ProposalData | undefined; - const azoriusContract = - baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - if (_metadata) { - const metaDataEvent: ProposalMetadata = JSON.parse(_metadata); - proposalData = { - metaData: { - title: metaDataEvent.title, - description: metaDataEvent.description, - documentationUrl: metaDataEvent.documentationUrl, - }, - transactions: transactions, - decodedTransactions: await decodeTransactions(transactions), - }; - } - let strategyContract: LinearERC20Voting | LinearERC721Voting; - if (ozLinearVotingContractAddress) { - strategyContract = - baseContracts.linearVotingMasterCopyContract.asProvider.attach(strategyAddress); - } else if (erc721LinearVotingContractAddress) { - strategyContract = - baseContracts.linearVotingERC721MasterCopyContract.asProvider.attach(strategyAddress); - } else { - logError('No strategy contract found'); - return []; - } - const func = async () => { - return mapProposalCreatedEventToProposal( - strategyContract, - strategyType, - proposalId, - proposer, - azoriusContract, - provider, - proposalData, - ); - }; - const proposal = await requestWithRetries(func, 5, 7000); - if (proposal) { - action.dispatch({ - type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, - payload: proposal, - }); - } - }, - [ - baseContracts, - azoriusContractAddress, - provider, - decodeTransactions, - action, - requestWithRetries, - strategyType, - ozLinearVotingContractAddress, - erc721LinearVotingContractAddress, - ], - ); + // const { requestWithRetries } = useAsyncRetry(); + // // Azrious proposals are listeners + // const proposalCreatedListener: TypedListener = useCallback( + // async (strategyAddress, proposalId, proposer, transactions, _metadata) => { + // if ( + // !azoriusContractAddress || + // !(ozLinearVotingContractAddress || erc721LinearVotingContractAddress) || + // !strategyType || + // !provider || + // !baseContracts + // ) { + // return; + // } + // let proposalData: ProposalData | undefined; + // const azoriusContract = + // baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); + // if (_metadata) { + // const metaDataEvent: ProposalMetadata = JSON.parse(_metadata); + // proposalData = { + // metaData: { + // title: metaDataEvent.title, + // description: metaDataEvent.description, + // documentationUrl: metaDataEvent.documentationUrl, + // }, + // transactions: transactions, + // decodedTransactions: await decodeTransactions(transactions), + // }; + // } + // let strategyContract: LinearERC20Voting | LinearERC721Voting; + // if (ozLinearVotingContractAddress) { + // strategyContract = + // baseContracts.linearVotingMasterCopyContract.asProvider.attach(strategyAddress); + // } else if (erc721LinearVotingContractAddress) { + // strategyContract = + // baseContracts.linearVotingERC721MasterCopyContract.asProvider.attach(strategyAddress); + // } else { + // logError('No strategy contract found'); + // return []; + // } + // const func = async () => { + // return mapProposalCreatedEventToProposal( + // strategyContract, + // strategyType, + // proposalId, + // proposer, + // azoriusContract, + // provider, + // proposalData, + // ); + // }; + // const proposal = await requestWithRetries(func, 5, 7000); + // if (proposal) { + // action.dispatch({ + // type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, + // payload: proposal, + // }); + // } + // }, + // [ + // baseContracts, + // azoriusContractAddress, + // provider, + // decodeTransactions, + // action, + // requestWithRetries, + // strategyType, + // ozLinearVotingContractAddress, + // erc721LinearVotingContractAddress, + // ], + // ); - const erc20ProposalVotedEventListener: TypedListener = useCallback( - async (voter, proposalId, support, weight) => { - if (!ozLinearVotingContractAddress || !strategyType || !baseContracts) { - return; - } - const strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - ozLinearVotingContractAddress, - ); + // const erc20ProposalVotedEventListener: TypedListener = useCallback( + // async (voter, proposalId, support, weight) => { + // if (!ozLinearVotingContractAddress || !strategyType || !baseContracts) { + // return; + // } + // const strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + // ozLinearVotingContractAddress, + // ); - const votesSummary = await getProposalVotesSummary( - strategyContract, - strategyType, - BigNumber.from(proposalId), - ); + // const votesSummary = await getProposalVotesSummary( + // strategyContract, + // strategyType, + // BigNumber.from(proposalId), + // ); - action.dispatch({ - type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC20_VOTE, - payload: { - proposalId: proposalId.toString(), - voter, - support, - weight, - votesSummary, - }, - }); - }, - [ozLinearVotingContractAddress, action, strategyType, baseContracts], - ); + // action.dispatch({ + // type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC20_VOTE, + // payload: { + // proposalId: proposalId.toString(), + // voter, + // support, + // weight, + // votesSummary, + // }, + // }); + // }, + // [ozLinearVotingContractAddress, action, strategyType, baseContracts], + // ); - const erc721ProposalVotedEventListener: TypedListener = useCallback( - async (voter, proposalId, support, tokenAddresses, tokenIds) => { - if (!erc721LinearVotingContractAddress || !strategyType || !baseContracts) { - return; - } - const strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - erc721LinearVotingContractAddress, - ); - const votesSummary = await getProposalVotesSummary( - strategyContract, - strategyType, - BigNumber.from(proposalId), - ); + // const erc721ProposalVotedEventListener: TypedListener = useCallback( + // async (voter, proposalId, support, tokenAddresses, tokenIds) => { + // if (!erc721LinearVotingContractAddress || !strategyType || !baseContracts) { + // return; + // } + // const strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + // erc721LinearVotingContractAddress, + // ); + // const votesSummary = await getProposalVotesSummary( + // strategyContract, + // strategyType, + // BigNumber.from(proposalId), + // ); - action.dispatch({ - type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC721_VOTE, - payload: { - proposalId: proposalId.toString(), - voter, - support, - tokenAddresses, - tokenIds: tokenIds.map(tokenId => tokenId.toString()), - votesSummary, - }, - }); - }, - [erc721LinearVotingContractAddress, action, strategyType, baseContracts], - ); + // action.dispatch({ + // type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC721_VOTE, + // payload: { + // proposalId: proposalId.toString(), + // voter, + // support, + // tokenAddresses, + // tokenIds: tokenIds.map(tokenId => tokenId.toString()), + // votesSummary, + // }, + // }); + // }, + // [erc721LinearVotingContractAddress, action, strategyType, baseContracts], + // ); - useEffect(() => { - if (!azoriusContractAddress || !baseContracts) { - return; - } + // useEffect(() => { + // if (!azoriusContractAddress || !baseContracts) { + // return; + // } - const azoriusContract = - baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); + // const azoriusContract = + // baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); + // const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); - azoriusContract.on(proposalCreatedFilter, proposalCreatedListener); + // azoriusContract.on(proposalCreatedFilter, proposalCreatedListener); - return () => { - azoriusContract.off(proposalCreatedFilter, proposalCreatedListener); - }; - }, [azoriusContractAddress, proposalCreatedListener, baseContracts]); + // return () => { + // azoriusContract.off(proposalCreatedFilter, proposalCreatedListener); + // }; + // }, [azoriusContractAddress, proposalCreatedListener, baseContracts]); - useEffect(() => { - if (ozLinearVotingContractAddress && baseContracts) { - const ozLinearVotingContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - ozLinearVotingContractAddress, - ); + // useEffect(() => { + // if (ozLinearVotingContractAddress && baseContracts) { + // const ozLinearVotingContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + // ozLinearVotingContractAddress, + // ); - const votedEvent = ozLinearVotingContract.filters.Voted(); + // const votedEvent = ozLinearVotingContract.filters.Voted(); - ozLinearVotingContract.on(votedEvent, erc20ProposalVotedEventListener); + // ozLinearVotingContract.on(votedEvent, erc20ProposalVotedEventListener); - return () => { - ozLinearVotingContract.off(votedEvent, erc20ProposalVotedEventListener); - }; - } else if (erc721LinearVotingContractAddress && baseContracts) { - const erc721LinearVotingContract = - baseContracts.linearVotingMasterCopyContract.asProvider.attach( - erc721LinearVotingContractAddress, - ); - const votedEvent = erc721LinearVotingContract.filters.Voted(); + // return () => { + // ozLinearVotingContract.off(votedEvent, erc20ProposalVotedEventListener); + // }; + // } else if (erc721LinearVotingContractAddress && baseContracts) { + // const erc721LinearVotingContract = + // baseContracts.linearVotingMasterCopyContract.asProvider.attach( + // erc721LinearVotingContractAddress, + // ); + // const votedEvent = erc721LinearVotingContract.filters.Voted(); - erc721LinearVotingContract.on(votedEvent, erc721ProposalVotedEventListener); + // erc721LinearVotingContract.on(votedEvent, erc721ProposalVotedEventListener); - return () => { - erc721LinearVotingContract.off(votedEvent, erc721ProposalVotedEventListener); - }; - } - }, [ - ozLinearVotingContractAddress, - erc721LinearVotingContractAddress, - erc20ProposalVotedEventListener, - erc721ProposalVotedEventListener, - baseContracts, - ]); + // return () => { + // erc721LinearVotingContract.off(votedEvent, erc721ProposalVotedEventListener); + // }; + // } + // }, [ + // ozLinearVotingContractAddress, + // erc721LinearVotingContractAddress, + // erc20ProposalVotedEventListener, + // erc721ProposalVotedEventListener, + // baseContracts, + // ]); return loadAzoriusProposals; }; From ab359c054f871753e3cb95a9da803eada8f4122a Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 16:45:02 -0400 Subject: [PATCH 05/26] Load proposals synchronously --- .../loaders/governance/useAzoriusProposals.ts | 77 +++++++++++-------- src/hooks/DAO/loaders/useProposals.ts | 17 +--- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index f05e8166c4..d28c336c82 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -49,7 +49,7 @@ export const useAzoriusProposals = () => { [decode], ); - const loadAzoriusProposals = useCallback(async (): Promise => { + const loadAzoriusProposals = useCallback(async () => { if ( !azoriusContractAddress || !(ozLinearVotingContractAddress || erc721LinearVotingContractAddress) || @@ -78,42 +78,55 @@ export const useAzoriusProposals = () => { return []; } - const proposals = await Promise.all( - proposalCreatedEvents.map(async ({ args }) => { - let proposalData; - if (args.metadata) { - const metadataEvent: ProposalMetadata = JSON.parse(args.metadata); - const decodedTransactions = await decodeTransactions(args.transactions); - proposalData = { - metaData: { - title: metadataEvent.title, - description: metadataEvent.description, - documentationUrl: metadataEvent.documentationUrl, - }, - transactions: args.transactions, - decodedTransactions, - }; - } - return mapProposalCreatedEventToProposal( - strategyContract, - strategyType, - args.proposalId, - args.proposer, - azoriusContract, - provider, - proposalData, - ); - }), - ); - return proposals; + const loadProposalFromEvent = async ({ args }: ProposalCreatedEvent) => { + let proposalData; + if (args.metadata) { + const metadataEvent: ProposalMetadata = JSON.parse(args.metadata); + const decodedTransactions = await decodeTransactions(args.transactions); + proposalData = { + metaData: { + title: metadataEvent.title, + description: metadataEvent.description, + documentationUrl: metadataEvent.documentationUrl, + }, + transactions: args.transactions, + decodedTransactions, + }; + } + return mapProposalCreatedEventToProposal( + strategyContract, + strategyType, + args.proposalId, + args.proposer, + azoriusContract, + provider, + proposalData, + ); + }; + + const loadProposalSynchronously = async ( + _proposalCreatedEvents: ProposalCreatedEvent[], + _loadProposalFromEvent: ({ args }: ProposalCreatedEvent) => Promise, + ) => { + for (const proposalCreatedEvent of _proposalCreatedEvents) { + const proposal = await _loadProposalFromEvent(proposalCreatedEvent); + action.dispatch({ + type: FractalGovernanceAction.SET_AZORIUS_PROPOSAL, + payload: proposal, + }); + } + }; + + await loadProposalSynchronously(proposalCreatedEvents.reverse(), loadProposalFromEvent); }, [ - decodeTransactions, + azoriusContractAddress, ozLinearVotingContractAddress, erc721LinearVotingContractAddress, - azoriusContractAddress, - provider, strategyType, + provider, baseContracts, + decodeTransactions, + action, ]); // const { requestWithRetries } = useAsyncRetry(); diff --git a/src/hooks/DAO/loaders/useProposals.ts b/src/hooks/DAO/loaders/useProposals.ts index 7d487f7c94..46724467aa 100644 --- a/src/hooks/DAO/loaders/useProposals.ts +++ b/src/hooks/DAO/loaders/useProposals.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react'; import { useFractal } from '../../../providers/App/AppProvider'; -import { FractalGovernanceAction } from '../../../providers/App/governance/action'; import { GovernanceType } from '../../../types'; import { useUpdateTimer } from '../../utils/useUpdateTimer'; import { useAzoriusProposals } from './governance/useAzoriusProposals'; @@ -10,7 +9,6 @@ export const useDAOProposals = () => { const { node: { daoAddress }, governance: { type }, - action, } = useFractal(); const { setMethodOnInterval, clearIntervals } = useUpdateTimer(daoAddress); @@ -21,23 +19,12 @@ export const useDAOProposals = () => { clearIntervals(); if (type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721) { // load Azorius proposals and strategies - const proposals = await loadAzoriusProposals(); - action.dispatch({ - type: FractalGovernanceAction.SET_PROPOSALS, - payload: proposals, - }); + await loadAzoriusProposals(); } else if (type === GovernanceType.MULTISIG) { // load mulisig proposals setMethodOnInterval(loadSafeMultisigProposals); } - }, [ - type, - loadAzoriusProposals, - action, - loadSafeMultisigProposals, - setMethodOnInterval, - clearIntervals, - ]); + }, [type, loadAzoriusProposals, loadSafeMultisigProposals, setMethodOnInterval, clearIntervals]); return loadDAOProposals; }; From 5409650a880873c29998be87abbb6f22d5f09dfb Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 18:34:36 -0400 Subject: [PATCH 06/26] Get all Voted and ProposalExecuted events one time, not per proposal --- .../loaders/governance/useAzoriusProposals.ts | 29 +++++++++++++++++-- src/utils/azorius.ts | 22 +++++++------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index d28c336c82..d74573d77a 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -1,6 +1,8 @@ import { LinearERC20Voting, LinearERC721Voting } from '@fractal-framework/fractal-contracts'; import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; +import { ProposalExecutedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; import { ProposalCreatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; +import { VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; import { BigNumber } from 'ethers'; @@ -78,7 +80,11 @@ export const useAzoriusProposals = () => { return []; } - const loadProposalFromEvent = async ({ args }: ProposalCreatedEvent) => { + const loadProposalFromEvent = async ( + { args }: ProposalCreatedEvent, + votedEvents: VotedEvent[], + executedEvents: ProposalExecutedEvent[], + ) => { let proposalData; if (args.metadata) { const metadataEvent: ProposalMetadata = JSON.parse(args.metadata); @@ -93,6 +99,7 @@ export const useAzoriusProposals = () => { decodedTransactions, }; } + return mapProposalCreatedEventToProposal( strategyContract, strategyType, @@ -100,16 +107,32 @@ export const useAzoriusProposals = () => { args.proposer, azoriusContract, provider, + votedEvents, + executedEvents, proposalData, ); }; const loadProposalSynchronously = async ( _proposalCreatedEvents: ProposalCreatedEvent[], - _loadProposalFromEvent: ({ args }: ProposalCreatedEvent) => Promise, + _loadProposalFromEvent: ( + { args }: ProposalCreatedEvent, + votedEvents: VotedEvent[], + executedEvents: ProposalExecutedEvent[], + ) => Promise, ) => { + const votedEventFilter = strategyContract.filters.Voted(); + const votedEvents = await strategyContract.queryFilter(votedEventFilter); + + const executedEventFilter = azoriusContract.filters.ProposalExecuted(); + const executedEvents = await azoriusContract.queryFilter(executedEventFilter); + for (const proposalCreatedEvent of _proposalCreatedEvents) { - const proposal = await _loadProposalFromEvent(proposalCreatedEvent); + const proposal = await _loadProposalFromEvent( + proposalCreatedEvent, + votedEvents, + executedEvents, + ); action.dispatch({ type: FractalGovernanceAction.SET_AZORIUS_PROPOSAL, payload: proposal, diff --git a/src/utils/azorius.ts b/src/utils/azorius.ts index 1061d44e8e..e8be31abb0 100644 --- a/src/utils/azorius.ts +++ b/src/utils/azorius.ts @@ -3,6 +3,8 @@ import { LinearERC20Voting, LinearERC721Voting, } from '@fractal-framework/fractal-contracts'; +import { ProposalExecutedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; +import { VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; import { SafeMultisigTransactionWithTransfersResponse } from '@safe-global/safe-service-client'; import { BigNumber } from 'ethers'; import { strategyFractalProposalStates } from '../constants/strategy'; @@ -90,12 +92,10 @@ export const getProposalVotesSummary = async ( } }; -export const getProposalVotes = async ( - strategyContract: LinearERC20Voting | LinearERC721Voting, +const getProposalVotes = ( + votes: VotedEvent[], proposalId: BigNumber, -): Promise => { - const voteEventFilter = strategyContract.filters.Voted(); - const votes = await strategyContract.queryFilter(voteEventFilter); +): ProposalVote[] | ERC721ProposalVote[] => { const proposalVotesEvent = votes.filter(voteEvent => proposalId.eq(voteEvent.args.proposalId)); return proposalVotesEvent.map(({ args: { voter, voteType, ...rest } }) => { @@ -114,6 +114,8 @@ export const mapProposalCreatedEventToProposal = async ( proposer: string, azoriusContract: Azorius, provider: Providers, + votedEvents: VotedEvent[], + executedEvents: ProposalExecutedEvent[], data?: ProposalData, ) => { const { endBlock, startBlock, abstainVotes, yesVotes, noVotes } = @@ -121,9 +123,11 @@ export const mapProposalCreatedEventToProposal = async ( const quorum = await getQuorum(strategyContract, strategyType, proposalId); const deadlineSeconds = await getTimeStamp(endBlock, provider); - const state = await getAzoriusProposalState(azoriusContract, proposalId); - const votes = await getProposalVotes(strategyContract, proposalId); const block = await provider.getBlock(startBlock); + + const state = await getAzoriusProposalState(azoriusContract, proposalId); + const votes = getProposalVotes(votedEvents, proposalId); + const votesSummary = { yes: yesVotes, no: noVotes, @@ -135,9 +139,7 @@ export const mapProposalCreatedEventToProposal = async ( let transactionHash: string | undefined; if (state === FractalProposalState.EXECUTED) { - const proposalExecutedFilter = azoriusContract.filters.ProposalExecuted(); - const proposalExecutedEvents = await azoriusContract.queryFilter(proposalExecutedFilter); - const executedEvent = proposalExecutedEvents.find(event => + const executedEvent = executedEvents.find(event => BigNumber.from(event.args[0]).eq(proposalId), ); transactionHash = executedEvent?.transactionHash; From 8fe9d1b07ba52abd95280af695c5b325bed48485 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 18:36:19 -0400 Subject: [PATCH 07/26] Rename function and do less param passing --- .../DAO/loaders/governance/useAzoriusProposals.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index d74573d77a..dca2e279ad 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -113,14 +113,7 @@ export const useAzoriusProposals = () => { ); }; - const loadProposalSynchronously = async ( - _proposalCreatedEvents: ProposalCreatedEvent[], - _loadProposalFromEvent: ( - { args }: ProposalCreatedEvent, - votedEvents: VotedEvent[], - executedEvents: ProposalExecutedEvent[], - ) => Promise, - ) => { + const loadProposalsSynchronously = async (_proposalCreatedEvents: ProposalCreatedEvent[]) => { const votedEventFilter = strategyContract.filters.Voted(); const votedEvents = await strategyContract.queryFilter(votedEventFilter); @@ -128,7 +121,7 @@ export const useAzoriusProposals = () => { const executedEvents = await azoriusContract.queryFilter(executedEventFilter); for (const proposalCreatedEvent of _proposalCreatedEvents) { - const proposal = await _loadProposalFromEvent( + const proposal = await loadProposalFromEvent( proposalCreatedEvent, votedEvents, executedEvents, @@ -140,7 +133,7 @@ export const useAzoriusProposals = () => { } }; - await loadProposalSynchronously(proposalCreatedEvents.reverse(), loadProposalFromEvent); + await loadProposalsSynchronously(proposalCreatedEvents.reverse()); }, [ azoriusContractAddress, ozLinearVotingContractAddress, From c7a63590c7a1caa1b4a6a11c2f1fd03942ccb0f9 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 20:04:23 -0400 Subject: [PATCH 08/26] Remove the @types/remove-markdown package, not used --- package-lock.json | 7 ------- package.json | 1 - 2 files changed, 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 826dcbfb2c..e31dd04549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,6 @@ "@testing-library/react": "^14.2.1", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", - "@types/remove-markdown": "^0.3.4", "@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/parser": "^6.19.0", "@vitejs/plugin-react": "^4.2.1", @@ -11650,12 +11649,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "peer": true }, - "node_modules/@types/remove-markdown": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.4.tgz", - "integrity": "sha512-i753EH/p02bw7bLlpfS/4CV1rdikbGiLabWyVsAvsFid3cA5RNU1frG7JycgY+NSnFwtoGlElvZVceCytecTDA==", - "dev": true - }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", diff --git a/package.json b/package.json index 283c986740..7e0ed9c52f 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "@testing-library/react": "^14.2.1", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", - "@types/remove-markdown": "^0.3.4", "@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/parser": "^6.19.0", "@vitejs/plugin-react": "^4.2.1", From bec53f2803c504ab695864443fa10a7ca266bdf4 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sat, 30 Mar 2024 20:13:22 -0400 Subject: [PATCH 09/26] If truncating Markdown, don't try to render URLs Currently we're truncating Markdown in Proposal List card, which is a giant link. Not supposed to render anchor tags within anchor tags. --- src/components/ui/proposal/Markdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/proposal/Markdown.tsx b/src/components/ui/proposal/Markdown.tsx index f1b63fe2fa..ce1bfa3b59 100644 --- a/src/components/ui/proposal/Markdown.tsx +++ b/src/components/ui/proposal/Markdown.tsx @@ -89,7 +89,7 @@ export default function Markdown({ truncate, content, collapsedLines = 6 }: IMar maxWidth="100%" > Date: Sun, 31 Mar 2024 15:52:19 -0400 Subject: [PATCH 10/26] Same event listener set up twice --- .../governance/useERC721LinearStrategy.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts index 95524cd032..516ab1cccf 100644 --- a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts @@ -117,25 +117,26 @@ export const useERC721LinearStrategy = () => { }; }, [erc721LinearVotingContractAddress, action, baseContracts, type]); - useEffect(() => { - if (!azoriusContractAddress || !baseContracts || !type) { - return; - } - const azoriusContract = - baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); + // this is duplicated in useERC20LinearStrategy line for line + // useEffect(() => { + // if (!azoriusContractAddress || !baseContracts) { + // return; + // } + // const azoriusContract = + // baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - const timeLockPeriodFilter = azoriusContract.filters.TimelockPeriodUpdated(); - const timelockPeriodListener: TypedListener = timelockPeriod => { - action.dispatch({ - type: FractalGovernanceAction.UPDATE_TIMELOCK_PERIOD, - payload: BigNumber.from(timelockPeriod), - }); - }; - azoriusContract.on(timeLockPeriodFilter, timelockPeriodListener); - return () => { - azoriusContract.off(timeLockPeriodFilter, timelockPeriodListener); - }; - }, [azoriusContractAddress, action, baseContracts, type]); + // const timeLockPeriodFilter = azoriusContract.filters.TimelockPeriodUpdated(); + // const timelockPeriodListener: TypedListener = timelockPeriod => { + // action.dispatch({ + // type: FractalGovernanceAction.UPDATE_TIMELOCK_PERIOD, + // payload: BigNumber.from(timelockPeriod), + // }); + // }; + // azoriusContract.on(timeLockPeriodFilter, timelockPeriodListener); + // return () => { + // azoriusContract.off(timeLockPeriodFilter, timelockPeriodListener); + // }; + // }, [azoriusContractAddress, action, baseContracts]); return loadERC721Strategy; }; From d3f39997d66473cb30c142afb73b789798eb23cb Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sun, 31 Mar 2024 15:52:52 -0400 Subject: [PATCH 11/26] Hm, not sure why these values are part of useeffect arrays --- .../loaders/governance/useERC20LinearStrategy.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts index 049a774be3..80b2ec4153 100644 --- a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts @@ -14,7 +14,6 @@ import { useTimeHelpers } from '../../../utils/useTimeHelpers'; export const useERC20LinearStrategy = () => { const { - governance: { type }, governanceContracts: { ozLinearVotingContractAddress, azoriusContractAddress }, action, } = useFractal(); @@ -68,7 +67,7 @@ export const useERC20LinearStrategy = () => { ]); useEffect(() => { - if (!ozLinearVotingContractAddress || !baseContracts || !type) { + if (!ozLinearVotingContractAddress || !baseContracts) { return; } const ozLinearVotingContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( @@ -86,10 +85,10 @@ export const useERC20LinearStrategy = () => { return () => { ozLinearVotingContract.off(votingPeriodfilter, listener); }; - }, [ozLinearVotingContractAddress, action, baseContracts, type]); + }, [ozLinearVotingContractAddress, action, baseContracts]); useEffect(() => { - if (!ozLinearVotingContractAddress || !baseContracts || !type) { + if (!ozLinearVotingContractAddress || !baseContracts) { return; } const ozLinearVotingContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( @@ -108,14 +107,15 @@ export const useERC20LinearStrategy = () => { return () => { ozLinearVotingContract.off(quorumNumeratorUpdatedFilter, quorumNumeratorUpdatedListener); }; - }, [ozLinearVotingContractAddress, action, baseContracts, type]); + }, [ozLinearVotingContractAddress, action, baseContracts]); useEffect(() => { - if (!azoriusContractAddress || !baseContracts || !type) { + if (!azoriusContractAddress || !baseContracts) { return; } const azoriusContract = baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); + const timeLockPeriodFilter = azoriusContract.filters.TimelockPeriodUpdated(); const timelockPeriodListener: TypedListener = timelockPeriod => { action.dispatch({ @@ -127,7 +127,7 @@ export const useERC20LinearStrategy = () => { return () => { azoriusContract.off(timeLockPeriodFilter, timelockPeriodListener); }; - }, [azoriusContractAddress, action, baseContracts, type]); + }, [azoriusContractAddress, action, baseContracts]); return loadERC20Strategy; }; From bf6af3f6a8d093afb421c91157a40497489cea46 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Sun, 31 Mar 2024 15:53:58 -0400 Subject: [PATCH 12/26] Broke out a lot of functions from useAzoriusProposals to top level of file, extensive use of dependency injection --- .../loaders/governance/useAzoriusProposals.ts | 247 ++++++++++-------- src/hooks/DAO/loaders/useProposals.ts | 23 +- 2 files changed, 151 insertions(+), 119 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index dca2e279ad..cd5f735dac 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -1,23 +1,112 @@ import { LinearERC20Voting, LinearERC721Voting } from '@fractal-framework/fractal-contracts'; -import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; -import { ProposalExecutedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; -import { ProposalCreatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; +import { + Azorius, + ProposalExecutedEvent, + ProposalCreatedEvent, +} from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; import { VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; -import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; -import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; -import { BigNumber } from 'ethers'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { logError } from '../../../../helpers/errorLogging'; import { useFractal } from '../../../../providers/App/AppProvider'; -import { FractalGovernanceAction } from '../../../../providers/App/governance/action'; import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; -import { ProposalMetadata, MetaTransaction, VotingStrategyType } from '../../../../types'; -import { AzoriusProposal, ProposalData } from '../../../../types/daoProposal'; -import { mapProposalCreatedEventToProposal, getProposalVotesSummary } from '../../../../utils'; +import { + ProposalMetadata, + MetaTransaction, + VotingStrategyType, + DecodedTransaction, +} from '../../../../types'; +import { AzoriusProposal } from '../../../../types/daoProposal'; +import { Providers } from '../../../../types/network'; +import { mapProposalCreatedEventToProposal } from '../../../../utils'; import useSafeContracts from '../../../safe/useSafeContracts'; import { useAsyncRetry } from '../../../utils/useAsyncRetry'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; +const decodeTransactions = async ( + _decode: (value: string, to: string, data?: string | undefined) => Promise, + _transactions: MetaTransaction[], +) => { + const decodedTransactions = await Promise.all( + _transactions.map(async tx => _decode(tx.value.toString(), tx.to, tx.data)), + ); + return decodedTransactions.flat(); +}; + +const loadProposalFromEvent = async ( + _provider: Providers, + _decode: (value: string, to: string, data?: string | undefined) => Promise, + _azoriusContract: Azorius, + _strategyContract: LinearERC20Voting | LinearERC721Voting, + _strategyType: VotingStrategyType, + { args: _args }: ProposalCreatedEvent, + _votedEvents: VotedEvent[], + _executedEvents: ProposalExecutedEvent[], +) => { + let proposalData; + if (_args.metadata) { + const metadataEvent: ProposalMetadata = JSON.parse(_args.metadata); + const decodedTransactions = await decodeTransactions(_decode, _args.transactions); + proposalData = { + metaData: { + title: metadataEvent.title, + description: metadataEvent.description, + documentationUrl: metadataEvent.documentationUrl, + }, + transactions: _args.transactions, + decodedTransactions, + }; + } + + const proposal = await mapProposalCreatedEventToProposal( + _strategyContract, + _strategyType, + _args.proposalId, + _args.proposer, + _azoriusContract, + _provider, + _votedEvents, + _executedEvents, + proposalData, + ); + + return proposal; +}; + +const loadAzoriusProposals = async ( + azoriusContract: Azorius, + strategyContract: LinearERC20Voting | LinearERC721Voting, + strategyType: VotingStrategyType, + provider: Providers, + decode: (value: string, to: string, data?: string | undefined) => Promise, + proposalLoaded: (proposal: AzoriusProposal) => void, +) => { + const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); + const proposalCreatedEvents = ( + await azoriusContract.queryFilter(proposalCreatedFilter) + ).reverse(); + + const votedEventFilter = strategyContract.filters.Voted(); + const votedEvents = await strategyContract.queryFilter(votedEventFilter); + + const proposalExecutedEventFilter = azoriusContract.filters.ProposalExecuted(); + const proposalExecutedEvents = await azoriusContract.queryFilter(proposalExecutedEventFilter); + + for (const proposalCreatedEvent of proposalCreatedEvents) { + const proposal = await loadProposalFromEvent( + provider, + decode, + azoriusContract, + strategyContract, + strategyType, + proposalCreatedEvent, + votedEvents, + proposalExecutedEvents, + ); + + proposalLoaded(proposal); + } +}; + export const useAzoriusProposals = () => { const { governanceContracts: { @@ -25,9 +114,8 @@ export const useAzoriusProposals = () => { ozLinearVotingContractAddress, erc721LinearVotingContractAddress, }, - action, } = useFractal(); - const baseContracts = useSafeContracts(); + const strategyType = useMemo(() => { if (ozLinearVotingContractAddress) { return VotingStrategyType.LINEAR_ERC20; @@ -37,113 +125,31 @@ export const useAzoriusProposals = () => { return undefined; } }, [ozLinearVotingContractAddress, erc721LinearVotingContractAddress]); + + const baseContracts = useSafeContracts(); const provider = useEthersProvider(); const decode = useSafeDecoder(); - const decodeTransactions = useCallback( - async (transactions: MetaTransaction[]) => { - const decodedTransactions = await Promise.all( - transactions.map(async tx => { - return decode(tx.value.toString(), tx.to, tx.data); - }), - ); - return decodedTransactions.flat(); - }, - [decode], - ); - - const loadAzoriusProposals = useCallback(async () => { - if ( - !azoriusContractAddress || - !(ozLinearVotingContractAddress || erc721LinearVotingContractAddress) || - !strategyType || - !provider || - !baseContracts - ) { - return []; - } - const azoriusContract = - baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); - - const proposalCreatedEvents = await azoriusContract.queryFilter(proposalCreatedFilter); - let strategyContract: LinearERC20Voting | LinearERC721Voting; - if (ozLinearVotingContractAddress) { - strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - ozLinearVotingContractAddress, - ); - } else if (erc721LinearVotingContractAddress) { - strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - erc721LinearVotingContractAddress, - ); - } else { - logError('No strategy contract found'); - return []; - } - const loadProposalFromEvent = async ( - { args }: ProposalCreatedEvent, - votedEvents: VotedEvent[], - executedEvents: ProposalExecutedEvent[], - ) => { - let proposalData; - if (args.metadata) { - const metadataEvent: ProposalMetadata = JSON.parse(args.metadata); - const decodedTransactions = await decodeTransactions(args.transactions); - proposalData = { - metaData: { - title: metadataEvent.title, - description: metadataEvent.description, - documentationUrl: metadataEvent.documentationUrl, - }, - transactions: args.transactions, - decodedTransactions, - }; - } - - return mapProposalCreatedEventToProposal( - strategyContract, - strategyType, - args.proposalId, - args.proposer, - azoriusContract, - provider, - votedEvents, - executedEvents, - proposalData, - ); - }; + if (!azoriusContractAddress || !strategyType || !provider || !baseContracts) { + return undefined; + } - const loadProposalsSynchronously = async (_proposalCreatedEvents: ProposalCreatedEvent[]) => { - const votedEventFilter = strategyContract.filters.Voted(); - const votedEvents = await strategyContract.queryFilter(votedEventFilter); - - const executedEventFilter = azoriusContract.filters.ProposalExecuted(); - const executedEvents = await azoriusContract.queryFilter(executedEventFilter); - - for (const proposalCreatedEvent of _proposalCreatedEvents) { - const proposal = await loadProposalFromEvent( - proposalCreatedEvent, - votedEvents, - executedEvents, - ); - action.dispatch({ - type: FractalGovernanceAction.SET_AZORIUS_PROPOSAL, - payload: proposal, - }); - } - }; + const azoriusContract = + baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - await loadProposalsSynchronously(proposalCreatedEvents.reverse()); - }, [ - azoriusContractAddress, - ozLinearVotingContractAddress, - erc721LinearVotingContractAddress, - strategyType, - provider, - baseContracts, - decodeTransactions, - action, - ]); + let strategyContract: LinearERC20Voting | LinearERC721Voting; + if (ozLinearVotingContractAddress) { + strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + ozLinearVotingContractAddress, + ); + } else if (erc721LinearVotingContractAddress) { + strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + erc721LinearVotingContractAddress, + ); + } else { + logError('No strategy contract found'); + return undefined; + } // const { requestWithRetries } = useAsyncRetry(); // // Azrious proposals are listeners @@ -324,5 +330,14 @@ export const useAzoriusProposals = () => { // baseContracts, // ]); - return loadAzoriusProposals; + return (proposalLoaded: (proposal: AzoriusProposal) => void) => { + return loadAzoriusProposals( + azoriusContract, + strategyContract, + strategyType, + provider, + decode, + proposalLoaded, + ); + }; }; diff --git a/src/hooks/DAO/loaders/useProposals.ts b/src/hooks/DAO/loaders/useProposals.ts index 46724467aa..9e7aaf7d8d 100644 --- a/src/hooks/DAO/loaders/useProposals.ts +++ b/src/hooks/DAO/loaders/useProposals.ts @@ -1,5 +1,6 @@ import { useCallback } from 'react'; import { useFractal } from '../../../providers/App/AppProvider'; +import { FractalGovernanceAction } from '../../../providers/App/governance/action'; import { GovernanceType } from '../../../types'; import { useUpdateTimer } from '../../utils/useUpdateTimer'; import { useAzoriusProposals } from './governance/useAzoriusProposals'; @@ -9,6 +10,7 @@ export const useDAOProposals = () => { const { node: { daoAddress }, governance: { type }, + action, } = useFractal(); const { setMethodOnInterval, clearIntervals } = useUpdateTimer(daoAddress); @@ -17,14 +19,29 @@ export const useDAOProposals = () => { const loadDAOProposals = useCallback(async () => { clearIntervals(); - if (type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721) { + if ( + (type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721) && + loadAzoriusProposals !== undefined + ) { // load Azorius proposals and strategies - await loadAzoriusProposals(); + loadAzoriusProposals(proposal => { + action.dispatch({ + type: FractalGovernanceAction.SET_AZORIUS_PROPOSAL, + payload: proposal, + }); + }); } else if (type === GovernanceType.MULTISIG) { // load mulisig proposals setMethodOnInterval(loadSafeMultisigProposals); } - }, [type, loadAzoriusProposals, loadSafeMultisigProposals, setMethodOnInterval, clearIntervals]); + }, [ + clearIntervals, + type, + loadAzoriusProposals, + action, + setMethodOnInterval, + loadSafeMultisigProposals, + ]); return loadDAOProposals; }; From c741273d6c04fcabdfc5361829dbf91fa3f16d38 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 2 Apr 2024 16:19:33 -0400 Subject: [PATCH 13/26] Remove fully reloading azorius proposals after creating one --- src/hooks/DAO/proposal/useSubmitProposal.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index 5b61885778..7daed94f9a 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -313,7 +313,6 @@ export default function useSubmitProposal() { }); setPendingCreateTx(true); - let success = false; try { const transactions = proposalData.targets.map((target, index) => ({ to: target, @@ -335,7 +334,7 @@ export default function useSubmitProposal() { }), ) ).wait(); - success = true; + console.log('success'); toast.dismiss(toastId); toast(successToastMessage); if (successCallback) { @@ -348,17 +347,8 @@ export default function useSubmitProposal() { } finally { setPendingCreateTx(false); } - - if (success) { - const averageBlockTime = await getAverageBlockTime(provider); - // Frequently there's an error in loadDAOProposals if we're loading the proposal immediately after proposal creation - // The error occurs because block of proposal creation not yet mined and trying to fetch underlying data of voting weight for new proposal fails with that error - // The code that throws an error: https://github.com/decent-dao/fractal-contracts/blob/develop/contracts/azorius/LinearERC20Voting.sol#L205-L211 - // So to avoid showing error toast - we're marking proposal creation as success and only then re-fetching proposals - setTimeout(loadDAOProposals, averageBlockTime * 1.5 * 1000); - } }, - [loadDAOProposals, provider, addressPrefix], + [provider, addressPrefix], ); const submitProposal = useCallback( From f813b305d6336df82ba2224eda7b3cce29d4cf05 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 2 Apr 2024 16:19:52 -0400 Subject: [PATCH 14/26] Big refactor to re-enable setting up event listeners on Azorius and Strategy contracts --- .../loaders/governance/useAzoriusProposals.ts | 512 ++++++++++-------- src/hooks/DAO/loaders/useProposals.ts | 71 ++- src/utils/azorius.ts | 143 +++-- 3 files changed, 461 insertions(+), 265 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index cd5f735dac..200b7a6cea 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -1,12 +1,14 @@ import { LinearERC20Voting, LinearERC721Voting } from '@fractal-framework/fractal-contracts'; +import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; import { Azorius, ProposalExecutedEvent, ProposalCreatedEvent, } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; -import { VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; -import { useMemo } from 'react'; -import { logError } from '../../../../helpers/errorLogging'; +import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; +import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; +import { BigNumber } from 'ethers'; +import { useEffect, useMemo } from 'react'; import { useFractal } from '../../../../providers/App/AppProvider'; import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { @@ -15,11 +17,10 @@ import { VotingStrategyType, DecodedTransaction, } from '../../../../types'; -import { AzoriusProposal } from '../../../../types/daoProposal'; +import { AzoriusProposal, ProposalVotesSummary } from '../../../../types/daoProposal'; import { Providers } from '../../../../types/network'; -import { mapProposalCreatedEventToProposal } from '../../../../utils'; +import { getProposalVotesSummary, mapProposalCreatedEventToProposal } from '../../../../utils'; import useSafeContracts from '../../../safe/useSafeContracts'; -import { useAsyncRetry } from '../../../utils/useAsyncRetry'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; const decodeTransactions = async ( @@ -36,11 +37,13 @@ const loadProposalFromEvent = async ( _provider: Providers, _decode: (value: string, to: string, data?: string | undefined) => Promise, _azoriusContract: Azorius, - _strategyContract: LinearERC20Voting | LinearERC721Voting, + _erc20StrategyContract: LinearERC20Voting | undefined, + _erc721StrategyContract: LinearERC721Voting | undefined, _strategyType: VotingStrategyType, { args: _args }: ProposalCreatedEvent, - _votedEvents: VotedEvent[], - _executedEvents: ProposalExecutedEvent[], + _erc20VotedEvents: Promise, + _erc721VotedEvents: Promise, + _executedEvents: Promise, ) => { let proposalData; if (_args.metadata) { @@ -58,13 +61,15 @@ const loadProposalFromEvent = async ( } const proposal = await mapProposalCreatedEventToProposal( - _strategyContract, + _erc20StrategyContract, + _erc721StrategyContract, _strategyType, _args.proposalId, _args.proposer, _azoriusContract, _provider, - _votedEvents, + _erc20VotedEvents, + _erc721VotedEvents, _executedEvents, proposalData, ); @@ -74,9 +79,13 @@ const loadProposalFromEvent = async ( const loadAzoriusProposals = async ( azoriusContract: Azorius, - strategyContract: LinearERC20Voting | LinearERC721Voting, + erc20StrategyContract: LinearERC20Voting | undefined, + erc721StrategyContract: LinearERC721Voting | undefined, strategyType: VotingStrategyType, provider: Providers, + erc20VotedEvents: Promise, + erc721VotedEvents: Promise, + executedEvents: Promise, decode: (value: string, to: string, data?: string | undefined) => Promise, proposalLoaded: (proposal: AzoriusProposal) => void, ) => { @@ -85,29 +94,136 @@ const loadAzoriusProposals = async ( await azoriusContract.queryFilter(proposalCreatedFilter) ).reverse(); - const votedEventFilter = strategyContract.filters.Voted(); - const votedEvents = await strategyContract.queryFilter(votedEventFilter); - - const proposalExecutedEventFilter = azoriusContract.filters.ProposalExecuted(); - const proposalExecutedEvents = await azoriusContract.queryFilter(proposalExecutedEventFilter); - for (const proposalCreatedEvent of proposalCreatedEvents) { const proposal = await loadProposalFromEvent( provider, decode, azoriusContract, - strategyContract, + erc20StrategyContract, + erc721StrategyContract, strategyType, proposalCreatedEvent, - votedEvents, - proposalExecutedEvents, + erc20VotedEvents, + erc721VotedEvents, + executedEvents, ); proposalLoaded(proposal); } }; -export const useAzoriusProposals = () => { +const proposalCreatedEventListener = ( + azoriusContract: Azorius, + erc20StrategyContract: LinearERC20Voting | undefined, + erc721StrategyContract: LinearERC721Voting | undefined, + erc20VotedEvents: Promise, + erc721VotedEvents: Promise, + executedEvents: Promise, + provider: Providers, + strategyType: VotingStrategyType, + decode: (value: string, to: string, data?: string | undefined) => Promise, + callback: (proposal: AzoriusProposal) => void, +): TypedListener => { + return async (_strategyAddress, proposalId, proposer, transactions, metadata) => { + console.log({ metadata }); + if (!metadata) { + return; + } + + const metaDataEvent: ProposalMetadata = JSON.parse(metadata); + const proposalData = { + metaData: { + title: metaDataEvent.title, + description: metaDataEvent.description, + documentationUrl: metaDataEvent.documentationUrl, + }, + transactions: transactions, + decodedTransactions: await decodeTransactions(decode, transactions), + }; + + const proposal = await mapProposalCreatedEventToProposal( + erc20StrategyContract, + erc721StrategyContract, + strategyType, + proposalId, + proposer, + azoriusContract, + provider, + erc20VotedEvents, + erc721VotedEvents, + executedEvents, + proposalData, + ); + + console.log({ proposal }); + + callback(proposal); + }; +}; + +const erc20VotedEventListener = ( + erc20StrategyContract: LinearERC20Voting, + strategyType: VotingStrategyType, + callback: ( + proposalId: number, + voter: string, + voteType: number, + weight: BigNumber, + votesSummary: ProposalVotesSummary, + ) => void, +): TypedListener => { + return async (voter, proposalId, voteType, weight) => { + const votesSummary = await getProposalVotesSummary( + erc20StrategyContract, + undefined, + strategyType, + BigNumber.from(proposalId), + ); + callback(proposalId, voter, voteType, weight, votesSummary); + }; +}; + +const erc721VotedEventListener = ( + erc721StrategyContract: LinearERC721Voting, + strategyType: VotingStrategyType, + callback: ( + proposalId: number, + voter: string, + voteType: number, + tokenAddresses: string[], + tokenIds: BigNumber[], + votesSummary: ProposalVotesSummary, + ) => void, +): TypedListener => { + return async (voter, proposalId, voteType, tokenAddresses, tokenIds) => { + const votesSummary = await getProposalVotesSummary( + undefined, + erc721StrategyContract, + strategyType, + BigNumber.from(proposalId), + ); + callback(proposalId, voter, voteType, tokenAddresses, tokenIds, votesSummary); + }; +}; + +export const useAzoriusProposals = ( + proposalCreatedEventCallback: (proposal: AzoriusProposal) => void, + erc20VotedEventCallback: ( + proposalId: number, + voter: string, + support: number, + weight: BigNumber, + votesSummary: ProposalVotesSummary, + ) => void, + erc721VotedEventCallback: ( + proposalId: number, + voter: string, + support: number, + tokenAddresses: string[], + tokenIds: BigNumber[], + votesSummary: ProposalVotesSummary, + ) => void, +) => { const { governanceContracts: { azoriusContractAddress, @@ -116,6 +232,18 @@ export const useAzoriusProposals = () => { }, } = useFractal(); + const baseContracts = useSafeContracts(); + const provider = useEthersProvider(); + const decode = useSafeDecoder(); + + const azoriusContract = useMemo(() => { + if (!baseContracts || !azoriusContractAddress) { + return; + } + + return baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); + }, [azoriusContractAddress, baseContracts]); + const strategyType = useMemo(() => { if (ozLinearVotingContractAddress) { return VotingStrategyType.LINEAR_ERC20; @@ -126,218 +254,152 @@ export const useAzoriusProposals = () => { } }, [ozLinearVotingContractAddress, erc721LinearVotingContractAddress]); - const baseContracts = useSafeContracts(); - const provider = useEthersProvider(); - const decode = useSafeDecoder(); - - if (!azoriusContractAddress || !strategyType || !provider || !baseContracts) { - return undefined; - } - - const azoriusContract = - baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); + const erc20StrategyContract = useMemo(() => { + if (!baseContracts || !ozLinearVotingContractAddress) { + return undefined; + } - let strategyContract: LinearERC20Voting | LinearERC721Voting; - if (ozLinearVotingContractAddress) { - strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + return baseContracts.linearVotingMasterCopyContract.asProvider.attach( ozLinearVotingContractAddress, ); - } else if (erc721LinearVotingContractAddress) { - strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + }, [baseContracts, ozLinearVotingContractAddress]); + + const erc721StrategyContract = useMemo(() => { + if (!baseContracts || !erc721LinearVotingContractAddress) { + return undefined; + } + + return baseContracts.linearVotingERC721MasterCopyContract.asProvider.attach( erc721LinearVotingContractAddress, ); - } else { - logError('No strategy contract found'); - return undefined; - } + }, [baseContracts, erc721LinearVotingContractAddress]); + + const erc20VotedEvents = useMemo(async () => { + if (!erc20StrategyContract) { + return; + } + + const filter = erc20StrategyContract.filters.Voted(); + const events = await erc20StrategyContract.queryFilter(filter); + + return events; + }, [erc20StrategyContract]); - // const { requestWithRetries } = useAsyncRetry(); - // // Azrious proposals are listeners - // const proposalCreatedListener: TypedListener = useCallback( - // async (strategyAddress, proposalId, proposer, transactions, _metadata) => { - // if ( - // !azoriusContractAddress || - // !(ozLinearVotingContractAddress || erc721LinearVotingContractAddress) || - // !strategyType || - // !provider || - // !baseContracts - // ) { - // return; - // } - // let proposalData: ProposalData | undefined; - // const azoriusContract = - // baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - // if (_metadata) { - // const metaDataEvent: ProposalMetadata = JSON.parse(_metadata); - // proposalData = { - // metaData: { - // title: metaDataEvent.title, - // description: metaDataEvent.description, - // documentationUrl: metaDataEvent.documentationUrl, - // }, - // transactions: transactions, - // decodedTransactions: await decodeTransactions(transactions), - // }; - // } - // let strategyContract: LinearERC20Voting | LinearERC721Voting; - // if (ozLinearVotingContractAddress) { - // strategyContract = - // baseContracts.linearVotingMasterCopyContract.asProvider.attach(strategyAddress); - // } else if (erc721LinearVotingContractAddress) { - // strategyContract = - // baseContracts.linearVotingERC721MasterCopyContract.asProvider.attach(strategyAddress); - // } else { - // logError('No strategy contract found'); - // return []; - // } - // const func = async () => { - // return mapProposalCreatedEventToProposal( - // strategyContract, - // strategyType, - // proposalId, - // proposer, - // azoriusContract, - // provider, - // proposalData, - // ); - // }; - // const proposal = await requestWithRetries(func, 5, 7000); - // if (proposal) { - // action.dispatch({ - // type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, - // payload: proposal, - // }); - // } - // }, - // [ - // baseContracts, - // azoriusContractAddress, - // provider, - // decodeTransactions, - // action, - // requestWithRetries, - // strategyType, - // ozLinearVotingContractAddress, - // erc721LinearVotingContractAddress, - // ], - // ); - - // const erc20ProposalVotedEventListener: TypedListener = useCallback( - // async (voter, proposalId, support, weight) => { - // if (!ozLinearVotingContractAddress || !strategyType || !baseContracts) { - // return; - // } - // const strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - // ozLinearVotingContractAddress, - // ); - - // const votesSummary = await getProposalVotesSummary( - // strategyContract, - // strategyType, - // BigNumber.from(proposalId), - // ); - - // action.dispatch({ - // type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC20_VOTE, - // payload: { - // proposalId: proposalId.toString(), - // voter, - // support, - // weight, - // votesSummary, - // }, - // }); - // }, - // [ozLinearVotingContractAddress, action, strategyType, baseContracts], - // ); - - // const erc721ProposalVotedEventListener: TypedListener = useCallback( - // async (voter, proposalId, support, tokenAddresses, tokenIds) => { - // if (!erc721LinearVotingContractAddress || !strategyType || !baseContracts) { - // return; - // } - // const strategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - // erc721LinearVotingContractAddress, - // ); - // const votesSummary = await getProposalVotesSummary( - // strategyContract, - // strategyType, - // BigNumber.from(proposalId), - // ); - - // action.dispatch({ - // type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC721_VOTE, - // payload: { - // proposalId: proposalId.toString(), - // voter, - // support, - // tokenAddresses, - // tokenIds: tokenIds.map(tokenId => tokenId.toString()), - // votesSummary, - // }, - // }); - // }, - // [erc721LinearVotingContractAddress, action, strategyType, baseContracts], - // ); - - // useEffect(() => { - // if (!azoriusContractAddress || !baseContracts) { - // return; - // } - - // const azoriusContract = - // baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - // const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); - - // azoriusContract.on(proposalCreatedFilter, proposalCreatedListener); - - // return () => { - // azoriusContract.off(proposalCreatedFilter, proposalCreatedListener); - // }; - // }, [azoriusContractAddress, proposalCreatedListener, baseContracts]); - - // useEffect(() => { - // if (ozLinearVotingContractAddress && baseContracts) { - // const ozLinearVotingContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( - // ozLinearVotingContractAddress, - // ); - - // const votedEvent = ozLinearVotingContract.filters.Voted(); - - // ozLinearVotingContract.on(votedEvent, erc20ProposalVotedEventListener); - - // return () => { - // ozLinearVotingContract.off(votedEvent, erc20ProposalVotedEventListener); - // }; - // } else if (erc721LinearVotingContractAddress && baseContracts) { - // const erc721LinearVotingContract = - // baseContracts.linearVotingMasterCopyContract.asProvider.attach( - // erc721LinearVotingContractAddress, - // ); - // const votedEvent = erc721LinearVotingContract.filters.Voted(); - - // erc721LinearVotingContract.on(votedEvent, erc721ProposalVotedEventListener); - - // return () => { - // erc721LinearVotingContract.off(votedEvent, erc721ProposalVotedEventListener); - // }; - // } - // }, [ - // ozLinearVotingContractAddress, - // erc721LinearVotingContractAddress, - // erc20ProposalVotedEventListener, - // erc721ProposalVotedEventListener, - // baseContracts, - // ]); - - return (proposalLoaded: (proposal: AzoriusProposal) => void) => { - return loadAzoriusProposals( + const erc721VotedEvents = useMemo(async () => { + if (!erc721StrategyContract) { + return; + } + + const filter = erc721StrategyContract.filters.Voted(); + const events = await erc721StrategyContract.queryFilter(filter); + + return events; + }, [erc721StrategyContract]); + + const executedEvents = useMemo(async () => { + if (!azoriusContract) { + return; + } + + const filter = azoriusContract.filters.ProposalExecuted(); + const events = await azoriusContract.queryFilter(filter); + + return events; + }, [azoriusContract]); + + useEffect(() => { + if (!azoriusContract || !provider || !strategyType) { + return; + } + + const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); + const listener = proposalCreatedEventListener( azoriusContract, - strategyContract, - strategyType, + erc20StrategyContract, + erc721StrategyContract, + erc20VotedEvents, + erc721VotedEvents, + executedEvents, provider, + strategyType, decode, - proposalLoaded, + proposalCreatedEventCallback, ); + + azoriusContract.on(proposalCreatedFilter, listener); + + return () => { + azoriusContract.off(proposalCreatedFilter, listener); + }; + }, [ + azoriusContract, + decode, + erc20StrategyContract, + erc20VotedEvents, + erc721StrategyContract, + erc721VotedEvents, + executedEvents, + proposalCreatedEventCallback, + provider, + strategyType, + ]); + + useEffect(() => { + if (strategyType !== VotingStrategyType.LINEAR_ERC20 || !erc20StrategyContract) { + return; + } + + const votedEvent = erc20StrategyContract.filters.Voted(); + const listener = erc20VotedEventListener( + erc20StrategyContract as LinearERC20Voting, + strategyType, + erc20VotedEventCallback, + ); + + erc20StrategyContract.on(votedEvent, listener); + + return () => { + erc20StrategyContract.off(votedEvent, listener); + }; + }, [erc20VotedEventCallback, erc20StrategyContract, strategyType]); + + useEffect(() => { + if (strategyType !== VotingStrategyType.LINEAR_ERC721 || !erc721StrategyContract) { + return; + } + + const votedEvent = erc721StrategyContract.filters.Voted(); + const listener = erc721VotedEventListener( + erc721StrategyContract, + strategyType, + erc721VotedEventCallback, + ); + + erc721StrategyContract.on(votedEvent, listener); + + return () => { + erc721StrategyContract.off(votedEvent, listener); + }; + }, [erc721VotedEventCallback, erc721StrategyContract, strategyType]); + + if (!azoriusContract || !strategyType || !provider) { + return undefined; + } + + return { + loadAzoriusProposals: (proposalLoaded: (proposal: AzoriusProposal) => void) => { + return loadAzoriusProposals( + azoriusContract, + erc20StrategyContract, + erc721StrategyContract, + strategyType, + provider, + erc20VotedEvents, + erc721VotedEvents, + executedEvents, + decode, + proposalLoaded, + ); + }, }; }; diff --git a/src/hooks/DAO/loaders/useProposals.ts b/src/hooks/DAO/loaders/useProposals.ts index 9e7aaf7d8d..7f4bee5db0 100644 --- a/src/hooks/DAO/loaders/useProposals.ts +++ b/src/hooks/DAO/loaders/useProposals.ts @@ -1,7 +1,8 @@ +import { BigNumber } from 'ethers'; import { useCallback } from 'react'; import { useFractal } from '../../../providers/App/AppProvider'; import { FractalGovernanceAction } from '../../../providers/App/governance/action'; -import { GovernanceType } from '../../../types'; +import { AzoriusProposal, GovernanceType, ProposalVotesSummary } from '../../../types'; import { useUpdateTimer } from '../../utils/useUpdateTimer'; import { useAzoriusProposals } from './governance/useAzoriusProposals'; import { useSafeMultisigProposals } from './governance/useSafeMultisigProposals'; @@ -13,18 +14,78 @@ export const useDAOProposals = () => { action, } = useFractal(); + const proposalCreatedEventCallback = useCallback( + (proposal: AzoriusProposal) => { + action.dispatch({ + type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, + payload: proposal, + }); + }, + [action], + ); + + const erc20VotedEventCallback = useCallback( + ( + proposalId: number, + voter: string, + support: number, + weight: BigNumber, + votesSummary: ProposalVotesSummary, + ) => { + action.dispatch({ + type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC20_VOTE, + payload: { + proposalId: proposalId.toString(), + voter, + support, + weight, + votesSummary, + }, + }); + }, + [action], + ); + + const erc721VotedEventCallback = useCallback( + ( + proposalId: number, + voter: string, + voteType: number, + tokenAddresses: string[], + tokenIds: BigNumber[], + votesSummary: ProposalVotesSummary, + ) => { + action.dispatch({ + type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC721_VOTE, + payload: { + proposalId: proposalId.toString(), + voter, + support: voteType, + tokenAddresses, + tokenIds: tokenIds.map(tokenId => tokenId.toString()), + votesSummary, + }, + }); + }, + [action], + ); + const { setMethodOnInterval, clearIntervals } = useUpdateTimer(daoAddress); - const loadAzoriusProposals = useAzoriusProposals(); + const azoriusProposals = useAzoriusProposals( + proposalCreatedEventCallback, + erc20VotedEventCallback, + erc721VotedEventCallback, + ); const loadSafeMultisigProposals = useSafeMultisigProposals(); const loadDAOProposals = useCallback(async () => { clearIntervals(); if ( (type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721) && - loadAzoriusProposals !== undefined + azoriusProposals !== undefined ) { // load Azorius proposals and strategies - loadAzoriusProposals(proposal => { + azoriusProposals.loadAzoriusProposals(proposal => { action.dispatch({ type: FractalGovernanceAction.SET_AZORIUS_PROPOSAL, payload: proposal, @@ -37,7 +98,7 @@ export const useDAOProposals = () => { }, [ clearIntervals, type, - loadAzoriusProposals, + azoriusProposals, action, setMethodOnInterval, loadSafeMultisigProposals, diff --git a/src/utils/azorius.ts b/src/utils/azorius.ts index e8be31abb0..71f947c15f 100644 --- a/src/utils/azorius.ts +++ b/src/utils/azorius.ts @@ -4,7 +4,8 @@ import { LinearERC721Voting, } from '@fractal-framework/fractal-contracts'; import { ProposalExecutedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; -import { VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; +import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; +import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; import { SafeMultisigTransactionWithTransfersResponse } from '@safe-global/safe-service-client'; import { BigNumber } from 'ethers'; import { strategyFractalProposalStates } from '../constants/strategy'; @@ -39,23 +40,24 @@ export const getAzoriusProposalState = async ( }; const getQuorum = async ( - strategyContract: LinearERC20Voting | LinearERC721Voting, + erc20StrategyContract: LinearERC20Voting | undefined, + erc721StrategyContract: LinearERC721Voting | undefined, strategyType: VotingStrategyType, proposalId: BigNumber, ) => { let quorum; - if (strategyType === VotingStrategyType.LINEAR_ERC20) { + if (strategyType === VotingStrategyType.LINEAR_ERC20 && erc20StrategyContract) { try { - quorum = await (strategyContract as LinearERC20Voting).quorumVotes(proposalId); + quorum = await erc20StrategyContract.quorumVotes(proposalId); } catch (e) { // For who knows reason - strategy.quorumVotes might give you an error // Seems like occuring when token deployment haven't worked properly logError('Error while getting strategy quorum', e); quorum = BigNumber.from(0); } - } else if (strategyType === VotingStrategyType.LINEAR_ERC721) { - quorum = await (strategyContract as LinearERC721Voting).quorumThreshold(); + } else if (strategyType === VotingStrategyType.LINEAR_ERC721 && erc721StrategyContract) { + quorum = await erc721StrategyContract.quorumThreshold(); } else { quorum = BigNumber.from(0); } @@ -64,19 +66,44 @@ const getQuorum = async ( }; export const getProposalVotesSummary = async ( - strategy: LinearERC20Voting | LinearERC721Voting, + erc20Strategy: LinearERC20Voting | undefined, + erc721Strategy: LinearERC721Voting | undefined, strategyType: VotingStrategyType, proposalId: BigNumber, ): Promise => { try { - const { yesVotes, noVotes, abstainVotes } = await strategy.getProposalVotes(proposalId); + if (erc20Strategy !== undefined && erc721Strategy !== undefined) { + logError("we don't support multiple strategy contracts"); + throw new Error("we don't support multiple strategy contracts"); + } - return { - yes: yesVotes, - no: noVotes, - abstain: abstainVotes, - quorum: await getQuorum(strategy, strategyType, proposalId), - }; + if (erc20Strategy !== undefined) { + const { yesVotes, noVotes, abstainVotes } = await erc20Strategy.getProposalVotes(proposalId); + + return { + yes: yesVotes, + no: noVotes, + abstain: abstainVotes, + quorum: await getQuorum(erc20Strategy, erc721Strategy, strategyType, proposalId), + }; + } else if (erc721Strategy !== undefined) { + const { yesVotes, noVotes, abstainVotes } = await erc721Strategy.getProposalVotes(proposalId); + + return { + yes: yesVotes, + no: noVotes, + abstain: abstainVotes, + quorum: await getQuorum(erc20Strategy, erc721Strategy, strategyType, proposalId), + }; + } else { + const zero = BigNumber.from(0); + return { + yes: zero, + no: zero, + abstain: zero, + quorum: zero, + }; + } } catch (e) { // Sometimes loading DAO proposals called in the moment when proposal was **just** created // Thus, calling `getProposalVotes` for such a proposal reverts with error @@ -93,45 +120,91 @@ export const getProposalVotesSummary = async ( }; const getProposalVotes = ( - votes: VotedEvent[], + erc20VotedEvents: ERC20VotedEvent[] | undefined, + erc721VotedEvents: ERC721VotedEvent[] | undefined, proposalId: BigNumber, -): ProposalVote[] | ERC721ProposalVote[] => { - const proposalVotesEvent = votes.filter(voteEvent => proposalId.eq(voteEvent.args.proposalId)); +): (ProposalVote | ERC721ProposalVote)[] => { + if (erc20VotedEvents !== undefined && erc721VotedEvents !== undefined) { + logError("two voting contracts? we don't support that."); + return []; + } - return proposalVotesEvent.map(({ args: { voter, voteType, ...rest } }) => { - return { + if (erc20VotedEvents !== undefined) { + const erc20ProposalVoteEvents = erc20VotedEvents.filter(voteEvent => + proposalId.eq(voteEvent.args.proposalId), + ); + + return erc20ProposalVoteEvents.map(({ args: { voter, voteType, ...rest } }) => ({ ...rest, voter, choice: VOTE_CHOICES[voteType], - }; - }); + })); + } else if (erc721VotedEvents !== undefined) { + const erc721ProposalVoteEvents = erc721VotedEvents.filter(voteEvent => + proposalId.eq(voteEvent.args.proposalId), + ); + + return erc721ProposalVoteEvents.map(({ args: { voter, voteType, tokenIds, ...rest } }) => ({ + ...rest, + voter, + choice: VOTE_CHOICES[voteType], + weight: BigNumber.from(1), + tokenIds: tokenIds.map(id => id.toString()), + })); + } + + return []; }; export const mapProposalCreatedEventToProposal = async ( - strategyContract: LinearERC20Voting | LinearERC721Voting, + erc20StrategyContract: LinearERC20Voting | undefined, + erc721StrategyContract: LinearERC721Voting | undefined, strategyType: VotingStrategyType, proposalId: BigNumber, proposer: string, azoriusContract: Azorius, provider: Providers, - votedEvents: VotedEvent[], - executedEvents: ProposalExecutedEvent[], + erc20VotedEvents: Promise, + erc721VotedEvents: Promise, + executedEvents: Promise, data?: ProposalData, ) => { - const { endBlock, startBlock, abstainVotes, yesVotes, noVotes } = - await strategyContract.getProposalVotes(proposalId); - const quorum = await getQuorum(strategyContract, strategyType, proposalId); + if (erc20StrategyContract !== undefined && erc721StrategyContract !== undefined) { + logError("we don't support multiple strategy contracts"); + throw new Error("we don't support multiple strategy contracts"); + } + + let proposalVotes = { + startBlock: 0, + endBlock: 0, + noVotes: BigNumber.from(0), + yesVotes: BigNumber.from(0), + abstainVotes: BigNumber.from(0), + }; + + if (erc20StrategyContract !== undefined) { + proposalVotes = await erc20StrategyContract.getProposalVotes(proposalId); + } else if (erc721StrategyContract !== undefined) { + proposalVotes = await erc721StrategyContract.getProposalVotes(proposalId); + } + + const quorum = await getQuorum( + erc20StrategyContract, + erc721StrategyContract, + strategyType, + proposalId, + ); - const deadlineSeconds = await getTimeStamp(endBlock, provider); - const block = await provider.getBlock(startBlock); + const deadlineSeconds = await getTimeStamp(proposalVotes.endBlock, provider); + const block = await provider.getBlock(proposalVotes.startBlock); const state = await getAzoriusProposalState(azoriusContract, proposalId); - const votes = getProposalVotes(votedEvents, proposalId); + const votes = getProposalVotes(await erc20VotedEvents, await erc721VotedEvents, proposalId); const votesSummary = { - yes: yesVotes, - no: noVotes, - abstain: abstainVotes, + yes: proposalVotes.yesVotes, + no: proposalVotes.noVotes, + abstain: proposalVotes.abstainVotes, quorum, }; @@ -139,7 +212,7 @@ export const mapProposalCreatedEventToProposal = async ( let transactionHash: string | undefined; if (state === FractalProposalState.EXECUTED) { - const executedEvent = executedEvents.find(event => + const executedEvent = (await executedEvents)?.find(event => BigNumber.from(event.args[0]).eq(proposalId), ); transactionHash = executedEvent?.transactionHash; @@ -151,7 +224,7 @@ export const mapProposalCreatedEventToProposal = async ( proposalId: proposalId.toString(), targets, proposer, - startBlock: BigNumber.from(startBlock), + startBlock: BigNumber.from(proposalVotes.startBlock), transactionHash, deadlineMs: deadlineSeconds * 1000, state, From 9502942df046a1bca054c59013f7190737962336 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 2 Apr 2024 16:21:46 -0400 Subject: [PATCH 15/26] Remove unneeded dependency --- src/hooks/DAO/loaders/useFractalNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index 502c5f9111..f6fe59942e 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -125,7 +125,7 @@ export const useFractalNode = ( payload: safeInfo, }); }, - [action, lookupModules, requestWithRetries, reset, safeAPI], + [action, lookupModules, reset, safeAPI], ); useEffect(() => { From cd9677938abf78e344235a88592724ed7d12d33b Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 08:46:56 -0400 Subject: [PATCH 16/26] Remove catch error, let's see if it keeps happening --- src/utils/azorius.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/utils/azorius.ts b/src/utils/azorius.ts index 71f947c15f..28ddedcff3 100644 --- a/src/utils/azorius.ts +++ b/src/utils/azorius.ts @@ -48,14 +48,7 @@ const getQuorum = async ( let quorum; if (strategyType === VotingStrategyType.LINEAR_ERC20 && erc20StrategyContract) { - try { - quorum = await erc20StrategyContract.quorumVotes(proposalId); - } catch (e) { - // For who knows reason - strategy.quorumVotes might give you an error - // Seems like occuring when token deployment haven't worked properly - logError('Error while getting strategy quorum', e); - quorum = BigNumber.from(0); - } + quorum = await erc20StrategyContract.quorumVotes(proposalId); } else if (strategyType === VotingStrategyType.LINEAR_ERC721 && erc721StrategyContract) { quorum = await erc721StrategyContract.quorumThreshold(); } else { From f9f8c343f1cd0136b6210cdad7e01ca8dcfc4f34 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 08:48:07 -0400 Subject: [PATCH 17/26] Move proposal creation / voted event listeners to own hook --- .../governance/useAzoriusProposalListeners.ts | 249 ++++++++++++++++++ .../loaders/governance/useAzoriusProposals.ts | 213 +-------------- src/hooks/DAO/loaders/useProposals.ts | 65 +---- src/hooks/DAO/useDAOController.ts | 2 + src/utils/azorius.ts | 11 + 5 files changed, 269 insertions(+), 271 deletions(-) create mode 100644 src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts new file mode 100644 index 0000000000..5cafe5215d --- /dev/null +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts @@ -0,0 +1,249 @@ +import { LinearERC20Voting, LinearERC721Voting } from '@fractal-framework/fractal-contracts'; +import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; +import { + Azorius, + ProposalCreatedEvent, +} from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; +import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; +import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; +import { BigNumber } from 'ethers'; +import { Dispatch, useEffect, useMemo } from 'react'; +import { useFractal } from '../../../../providers/App/AppProvider'; +import { FractalGovernanceAction } from '../../../../providers/App/governance/action'; +import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; +import { + ProposalMetadata, + VotingStrategyType, + DecodedTransaction, + FractalActions, +} from '../../../../types'; +import { Providers } from '../../../../types/network'; +import { + getProposalVotesSummary, + mapProposalCreatedEventToProposal, + decodeTransactions, +} from '../../../../utils'; +import useSafeContracts from '../../../safe/useSafeContracts'; +import { useSafeDecoder } from '../../../utils/useSafeDecoder'; + +const proposalCreatedEventListener = ( + azoriusContract: Azorius, + erc20StrategyContract: LinearERC20Voting | undefined, + erc721StrategyContract: LinearERC721Voting | undefined, + provider: Providers, + strategyType: VotingStrategyType, + decode: (value: string, to: string, data?: string | undefined) => Promise, + dispatch: Dispatch, +): TypedListener => { + return async (_strategyAddress, proposalId, proposer, transactions, metadata) => { + console.log({ metadata }); + if (!metadata) { + return; + } + + const metaDataEvent: ProposalMetadata = JSON.parse(metadata); + const proposalData = { + metaData: { + title: metaDataEvent.title, + description: metaDataEvent.description, + documentationUrl: metaDataEvent.documentationUrl, + }, + transactions: transactions, + decodedTransactions: await decodeTransactions(decode, transactions), + }; + + const proposal = await mapProposalCreatedEventToProposal( + erc20StrategyContract, + erc721StrategyContract, + strategyType, + proposalId, + proposer, + azoriusContract, + provider, + Promise.resolve(undefined), + Promise.resolve(undefined), + Promise.resolve(undefined), + proposalData, + ); + + console.log({ proposal }); + + dispatch({ + type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, + payload: proposal, + }); + }; +}; + +const erc20VotedEventListener = ( + erc20StrategyContract: LinearERC20Voting, + strategyType: VotingStrategyType, + dispatch: Dispatch, +): TypedListener => { + return async (voter, proposalId, voteType, weight) => { + const votesSummary = await getProposalVotesSummary( + erc20StrategyContract, + undefined, + strategyType, + BigNumber.from(proposalId), + ); + + dispatch({ + type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC20_VOTE, + payload: { + proposalId: proposalId.toString(), + voter, + support: voteType, + weight, + votesSummary, + }, + }); + }; +}; + +const erc721VotedEventListener = ( + erc721StrategyContract: LinearERC721Voting, + strategyType: VotingStrategyType, + dispatch: Dispatch, +): TypedListener => { + return async (voter, proposalId, voteType, tokenAddresses, tokenIds) => { + const votesSummary = await getProposalVotesSummary( + undefined, + erc721StrategyContract, + strategyType, + BigNumber.from(proposalId), + ); + + dispatch({ + type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC721_VOTE, + payload: { + proposalId: proposalId.toString(), + voter, + support: voteType, + tokenAddresses, + tokenIds: tokenIds.map(tokenId => tokenId.toString()), + votesSummary, + }, + }); + }; +}; + +export const useAzoriusProposalListeners = () => { + const { + action, + governanceContracts: { + azoriusContractAddress, + ozLinearVotingContractAddress, + erc721LinearVotingContractAddress, + }, + } = useFractal(); + + const baseContracts = useSafeContracts(); + const provider = useEthersProvider(); + const decode = useSafeDecoder(); + + const azoriusContract = useMemo(() => { + if (!baseContracts || !azoriusContractAddress) { + return; + } + + return baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); + }, [azoriusContractAddress, baseContracts]); + + const strategyType = useMemo(() => { + if (ozLinearVotingContractAddress) { + return VotingStrategyType.LINEAR_ERC20; + } else if (erc721LinearVotingContractAddress) { + return VotingStrategyType.LINEAR_ERC721; + } else { + return undefined; + } + }, [ozLinearVotingContractAddress, erc721LinearVotingContractAddress]); + + const erc20StrategyContract = useMemo(() => { + if (!baseContracts || !ozLinearVotingContractAddress) { + return undefined; + } + + return baseContracts.linearVotingMasterCopyContract.asProvider.attach( + ozLinearVotingContractAddress, + ); + }, [baseContracts, ozLinearVotingContractAddress]); + + const erc721StrategyContract = useMemo(() => { + if (!baseContracts || !erc721LinearVotingContractAddress) { + return undefined; + } + + return baseContracts.linearVotingERC721MasterCopyContract.asProvider.attach( + erc721LinearVotingContractAddress, + ); + }, [baseContracts, erc721LinearVotingContractAddress]); + + useEffect(() => { + if (!azoriusContract || !provider || !strategyType) { + return; + } + + const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); + const listener = proposalCreatedEventListener( + azoriusContract, + erc20StrategyContract, + erc721StrategyContract, + provider, + strategyType, + decode, + action.dispatch, + ); + + azoriusContract.on(proposalCreatedFilter, listener); + + return () => { + azoriusContract.off(proposalCreatedFilter, listener); + }; + }, [ + action.dispatch, + azoriusContract, + decode, + erc20StrategyContract, + erc721StrategyContract, + provider, + strategyType, + ]); + + useEffect(() => { + if (strategyType !== VotingStrategyType.LINEAR_ERC20 || !erc20StrategyContract) { + return; + } + + const votedEvent = erc20StrategyContract.filters.Voted(); + const listener = erc20VotedEventListener(erc20StrategyContract, strategyType, action.dispatch); + + console.log('creating erc20 voted listener'); + erc20StrategyContract.on(votedEvent, listener); + + return () => { + console.log('removing erc20 voted listener'); + erc20StrategyContract.off(votedEvent, listener); + }; + }, [action.dispatch, erc20StrategyContract, strategyType]); + + useEffect(() => { + if (strategyType !== VotingStrategyType.LINEAR_ERC721 || !erc721StrategyContract) { + return; + } + + const votedEvent = erc721StrategyContract.filters.Voted(); + const listener = erc721VotedEventListener( + erc721StrategyContract, + strategyType, + action.dispatch, + ); + + erc721StrategyContract.on(votedEvent, listener); + + return () => { + erc721StrategyContract.off(votedEvent, listener); + }; + }, [action.dispatch, erc721StrategyContract, strategyType]); +}; diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 200b7a6cea..52e61fb401 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -1,5 +1,4 @@ import { LinearERC20Voting, LinearERC721Voting } from '@fractal-framework/fractal-contracts'; -import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; import { Azorius, ProposalExecutedEvent, @@ -7,32 +6,16 @@ import { } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; -import { BigNumber } from 'ethers'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { useFractal } from '../../../../providers/App/AppProvider'; import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; -import { - ProposalMetadata, - MetaTransaction, - VotingStrategyType, - DecodedTransaction, -} from '../../../../types'; -import { AzoriusProposal, ProposalVotesSummary } from '../../../../types/daoProposal'; +import { ProposalMetadata, VotingStrategyType, DecodedTransaction } from '../../../../types'; +import { AzoriusProposal } from '../../../../types/daoProposal'; import { Providers } from '../../../../types/network'; -import { getProposalVotesSummary, mapProposalCreatedEventToProposal } from '../../../../utils'; +import { mapProposalCreatedEventToProposal, decodeTransactions } from '../../../../utils'; import useSafeContracts from '../../../safe/useSafeContracts'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; -const decodeTransactions = async ( - _decode: (value: string, to: string, data?: string | undefined) => Promise, - _transactions: MetaTransaction[], -) => { - const decodedTransactions = await Promise.all( - _transactions.map(async tx => _decode(tx.value.toString(), tx.to, tx.data)), - ); - return decodedTransactions.flat(); -}; - const loadProposalFromEvent = async ( _provider: Providers, _decode: (value: string, to: string, data?: string | undefined) => Promise, @@ -112,118 +95,7 @@ const loadAzoriusProposals = async ( } }; -const proposalCreatedEventListener = ( - azoriusContract: Azorius, - erc20StrategyContract: LinearERC20Voting | undefined, - erc721StrategyContract: LinearERC721Voting | undefined, - erc20VotedEvents: Promise, - erc721VotedEvents: Promise, - executedEvents: Promise, - provider: Providers, - strategyType: VotingStrategyType, - decode: (value: string, to: string, data?: string | undefined) => Promise, - callback: (proposal: AzoriusProposal) => void, -): TypedListener => { - return async (_strategyAddress, proposalId, proposer, transactions, metadata) => { - console.log({ metadata }); - if (!metadata) { - return; - } - - const metaDataEvent: ProposalMetadata = JSON.parse(metadata); - const proposalData = { - metaData: { - title: metaDataEvent.title, - description: metaDataEvent.description, - documentationUrl: metaDataEvent.documentationUrl, - }, - transactions: transactions, - decodedTransactions: await decodeTransactions(decode, transactions), - }; - - const proposal = await mapProposalCreatedEventToProposal( - erc20StrategyContract, - erc721StrategyContract, - strategyType, - proposalId, - proposer, - azoriusContract, - provider, - erc20VotedEvents, - erc721VotedEvents, - executedEvents, - proposalData, - ); - - console.log({ proposal }); - - callback(proposal); - }; -}; - -const erc20VotedEventListener = ( - erc20StrategyContract: LinearERC20Voting, - strategyType: VotingStrategyType, - callback: ( - proposalId: number, - voter: string, - voteType: number, - weight: BigNumber, - votesSummary: ProposalVotesSummary, - ) => void, -): TypedListener => { - return async (voter, proposalId, voteType, weight) => { - const votesSummary = await getProposalVotesSummary( - erc20StrategyContract, - undefined, - strategyType, - BigNumber.from(proposalId), - ); - callback(proposalId, voter, voteType, weight, votesSummary); - }; -}; - -const erc721VotedEventListener = ( - erc721StrategyContract: LinearERC721Voting, - strategyType: VotingStrategyType, - callback: ( - proposalId: number, - voter: string, - voteType: number, - tokenAddresses: string[], - tokenIds: BigNumber[], - votesSummary: ProposalVotesSummary, - ) => void, -): TypedListener => { - return async (voter, proposalId, voteType, tokenAddresses, tokenIds) => { - const votesSummary = await getProposalVotesSummary( - undefined, - erc721StrategyContract, - strategyType, - BigNumber.from(proposalId), - ); - callback(proposalId, voter, voteType, tokenAddresses, tokenIds, votesSummary); - }; -}; - -export const useAzoriusProposals = ( - proposalCreatedEventCallback: (proposal: AzoriusProposal) => void, - erc20VotedEventCallback: ( - proposalId: number, - voter: string, - support: number, - weight: BigNumber, - votesSummary: ProposalVotesSummary, - ) => void, - erc721VotedEventCallback: ( - proposalId: number, - voter: string, - support: number, - tokenAddresses: string[], - tokenIds: BigNumber[], - votesSummary: ProposalVotesSummary, - ) => void, -) => { +export const useAzoriusProposals = () => { const { governanceContracts: { azoriusContractAddress, @@ -307,81 +179,6 @@ export const useAzoriusProposals = ( return events; }, [azoriusContract]); - useEffect(() => { - if (!azoriusContract || !provider || !strategyType) { - return; - } - - const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); - const listener = proposalCreatedEventListener( - azoriusContract, - erc20StrategyContract, - erc721StrategyContract, - erc20VotedEvents, - erc721VotedEvents, - executedEvents, - provider, - strategyType, - decode, - proposalCreatedEventCallback, - ); - - azoriusContract.on(proposalCreatedFilter, listener); - - return () => { - azoriusContract.off(proposalCreatedFilter, listener); - }; - }, [ - azoriusContract, - decode, - erc20StrategyContract, - erc20VotedEvents, - erc721StrategyContract, - erc721VotedEvents, - executedEvents, - proposalCreatedEventCallback, - provider, - strategyType, - ]); - - useEffect(() => { - if (strategyType !== VotingStrategyType.LINEAR_ERC20 || !erc20StrategyContract) { - return; - } - - const votedEvent = erc20StrategyContract.filters.Voted(); - const listener = erc20VotedEventListener( - erc20StrategyContract as LinearERC20Voting, - strategyType, - erc20VotedEventCallback, - ); - - erc20StrategyContract.on(votedEvent, listener); - - return () => { - erc20StrategyContract.off(votedEvent, listener); - }; - }, [erc20VotedEventCallback, erc20StrategyContract, strategyType]); - - useEffect(() => { - if (strategyType !== VotingStrategyType.LINEAR_ERC721 || !erc721StrategyContract) { - return; - } - - const votedEvent = erc721StrategyContract.filters.Voted(); - const listener = erc721VotedEventListener( - erc721StrategyContract, - strategyType, - erc721VotedEventCallback, - ); - - erc721StrategyContract.on(votedEvent, listener); - - return () => { - erc721StrategyContract.off(votedEvent, listener); - }; - }, [erc721VotedEventCallback, erc721StrategyContract, strategyType]); - if (!azoriusContract || !strategyType || !provider) { return undefined; } diff --git a/src/hooks/DAO/loaders/useProposals.ts b/src/hooks/DAO/loaders/useProposals.ts index 7f4bee5db0..0e1c47d046 100644 --- a/src/hooks/DAO/loaders/useProposals.ts +++ b/src/hooks/DAO/loaders/useProposals.ts @@ -1,8 +1,7 @@ -import { BigNumber } from 'ethers'; import { useCallback } from 'react'; import { useFractal } from '../../../providers/App/AppProvider'; import { FractalGovernanceAction } from '../../../providers/App/governance/action'; -import { AzoriusProposal, GovernanceType, ProposalVotesSummary } from '../../../types'; +import { GovernanceType } from '../../../types'; import { useUpdateTimer } from '../../utils/useUpdateTimer'; import { useAzoriusProposals } from './governance/useAzoriusProposals'; import { useSafeMultisigProposals } from './governance/useSafeMultisigProposals'; @@ -14,68 +13,8 @@ export const useDAOProposals = () => { action, } = useFractal(); - const proposalCreatedEventCallback = useCallback( - (proposal: AzoriusProposal) => { - action.dispatch({ - type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, - payload: proposal, - }); - }, - [action], - ); - - const erc20VotedEventCallback = useCallback( - ( - proposalId: number, - voter: string, - support: number, - weight: BigNumber, - votesSummary: ProposalVotesSummary, - ) => { - action.dispatch({ - type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC20_VOTE, - payload: { - proposalId: proposalId.toString(), - voter, - support, - weight, - votesSummary, - }, - }); - }, - [action], - ); - - const erc721VotedEventCallback = useCallback( - ( - proposalId: number, - voter: string, - voteType: number, - tokenAddresses: string[], - tokenIds: BigNumber[], - votesSummary: ProposalVotesSummary, - ) => { - action.dispatch({ - type: FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC721_VOTE, - payload: { - proposalId: proposalId.toString(), - voter, - support: voteType, - tokenAddresses, - tokenIds: tokenIds.map(tokenId => tokenId.toString()), - votesSummary, - }, - }); - }, - [action], - ); - const { setMethodOnInterval, clearIntervals } = useUpdateTimer(daoAddress); - const azoriusProposals = useAzoriusProposals( - proposalCreatedEventCallback, - erc20VotedEventCallback, - erc721VotedEventCallback, - ); + const azoriusProposals = useAzoriusProposals(); const loadSafeMultisigProposals = useSafeMultisigProposals(); const loadDAOProposals = useCallback(async () => { diff --git a/src/hooks/DAO/useDAOController.ts b/src/hooks/DAO/useDAOController.ts index 1f09864fae..32a30d5b2a 100644 --- a/src/hooks/DAO/useDAOController.ts +++ b/src/hooks/DAO/useDAOController.ts @@ -2,6 +2,7 @@ import { utils } from 'ethers'; import { useSearchParams } from 'react-router-dom'; import { useFractal } from '../../providers/App/AppProvider'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; +import { useAzoriusProposalListeners } from './loaders/governance/useAzoriusProposalListeners'; import { useERC20Claim } from './loaders/governance/useERC20Claim'; import { useSnapshotProposals } from './loaders/snapshot/useSnapshotProposals'; import { useFractalFreeze } from './loaders/useFractalFreeze'; @@ -48,6 +49,7 @@ export default function useDAOController() { useFractalTreasury(); useERC20Claim(); useSnapshotProposals(); + useAzoriusProposalListeners(); return { invalidQuery, wrongNetwork, errorLoading }; } diff --git a/src/utils/azorius.ts b/src/utils/azorius.ts index 28ddedcff3..7ee1edfe92 100644 --- a/src/utils/azorius.ts +++ b/src/utils/azorius.ts @@ -27,6 +27,7 @@ import { DecodedTransaction, VotingStrategyType, ERC721ProposalVote, + MetaTransaction, } from '../types'; import { Providers } from '../types/network'; import { getTimeStamp } from './contract'; @@ -293,3 +294,13 @@ export const parseDecodedData = ( export function getAzoriusModuleFromModules(modules: FractalModuleData[]) { return modules.find(module => module.moduleType === FractalModuleType.AZORIUS); } + +export const decodeTransactions = async ( + _decode: (value: string, to: string, data?: string | undefined) => Promise, + _transactions: MetaTransaction[], +) => { + const decodedTransactions = await Promise.all( + _transactions.map(async tx => _decode(tx.value.toString(), tx.to, tx.data)), + ); + return decodedTransactions.flat(); +}; From 8a053702e2538532df6cecf38ba883d7037d195d Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 11:22:59 -0400 Subject: [PATCH 18/26] Remove dev testing console.logs --- .../DAO/loaders/governance/useAzoriusProposalListeners.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts index 5cafe5215d..2f0f407451 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts @@ -36,7 +36,6 @@ const proposalCreatedEventListener = ( dispatch: Dispatch, ): TypedListener => { return async (_strategyAddress, proposalId, proposer, transactions, metadata) => { - console.log({ metadata }); if (!metadata) { return; } @@ -66,8 +65,6 @@ const proposalCreatedEventListener = ( proposalData, ); - console.log({ proposal }); - dispatch({ type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, payload: proposal, @@ -219,11 +216,9 @@ export const useAzoriusProposalListeners = () => { const votedEvent = erc20StrategyContract.filters.Voted(); const listener = erc20VotedEventListener(erc20StrategyContract, strategyType, action.dispatch); - console.log('creating erc20 voted listener'); erc20StrategyContract.on(votedEvent, listener); return () => { - console.log('removing erc20 voted listener'); erc20StrategyContract.off(votedEvent, listener); }; }, [action.dispatch, erc20StrategyContract, strategyType]); From 7c457970f17f49792ba17aca5d05f128036ff084 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 11:31:21 -0400 Subject: [PATCH 19/26] Remove unnecessary function --- .../loaders/governance/useAzoriusProposals.ts | 74 ++++++------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 52e61fb401..07aa32265b 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -2,7 +2,6 @@ import { LinearERC20Voting, LinearERC721Voting } from '@fractal-framework/fracta import { Azorius, ProposalExecutedEvent, - ProposalCreatedEvent, } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; @@ -16,50 +15,6 @@ import { mapProposalCreatedEventToProposal, decodeTransactions } from '../../../ import useSafeContracts from '../../../safe/useSafeContracts'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; -const loadProposalFromEvent = async ( - _provider: Providers, - _decode: (value: string, to: string, data?: string | undefined) => Promise, - _azoriusContract: Azorius, - _erc20StrategyContract: LinearERC20Voting | undefined, - _erc721StrategyContract: LinearERC721Voting | undefined, - _strategyType: VotingStrategyType, - { args: _args }: ProposalCreatedEvent, - _erc20VotedEvents: Promise, - _erc721VotedEvents: Promise, - _executedEvents: Promise, -) => { - let proposalData; - if (_args.metadata) { - const metadataEvent: ProposalMetadata = JSON.parse(_args.metadata); - const decodedTransactions = await decodeTransactions(_decode, _args.transactions); - proposalData = { - metaData: { - title: metadataEvent.title, - description: metadataEvent.description, - documentationUrl: metadataEvent.documentationUrl, - }, - transactions: _args.transactions, - decodedTransactions, - }; - } - - const proposal = await mapProposalCreatedEventToProposal( - _erc20StrategyContract, - _erc721StrategyContract, - _strategyType, - _args.proposalId, - _args.proposer, - _azoriusContract, - _provider, - _erc20VotedEvents, - _erc721VotedEvents, - _executedEvents, - proposalData, - ); - - return proposal; -}; - const loadAzoriusProposals = async ( azoriusContract: Azorius, erc20StrategyContract: LinearERC20Voting | undefined, @@ -78,17 +33,36 @@ const loadAzoriusProposals = async ( ).reverse(); for (const proposalCreatedEvent of proposalCreatedEvents) { - const proposal = await loadProposalFromEvent( - provider, - decode, - azoriusContract, + let proposalData; + if (proposalCreatedEvent.args.metadata) { + const metadataEvent: ProposalMetadata = JSON.parse(proposalCreatedEvent.args.metadata); + const decodedTransactions = await decodeTransactions( + decode, + proposalCreatedEvent.args.transactions, + ); + proposalData = { + metaData: { + title: metadataEvent.title, + description: metadataEvent.description, + documentationUrl: metadataEvent.documentationUrl, + }, + transactions: proposalCreatedEvent.args.transactions, + decodedTransactions, + }; + } + + const proposal = await mapProposalCreatedEventToProposal( erc20StrategyContract, erc721StrategyContract, strategyType, - proposalCreatedEvent, + proposalCreatedEvent.args.proposalId, + proposalCreatedEvent.args.proposer, + azoriusContract, + provider, erc20VotedEvents, erc721VotedEvents, executedEvents, + proposalData, ); proposalLoaded(proposal); From fa26f0145dc9be74640dc2d65cac13a5ca4bb570 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 13:58:55 -0400 Subject: [PATCH 20/26] Remove another unneeded object --- .../loaders/governance/useAzoriusProposals.ts | 28 +++++++++---------- src/hooks/DAO/loaders/useProposals.ts | 8 +++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 07aa32265b..b7066f8130 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -157,20 +157,18 @@ export const useAzoriusProposals = () => { return undefined; } - return { - loadAzoriusProposals: (proposalLoaded: (proposal: AzoriusProposal) => void) => { - return loadAzoriusProposals( - azoriusContract, - erc20StrategyContract, - erc721StrategyContract, - strategyType, - provider, - erc20VotedEvents, - erc721VotedEvents, - executedEvents, - decode, - proposalLoaded, - ); - }, + return (proposalLoaded: (proposal: AzoriusProposal) => void) => { + return loadAzoriusProposals( + azoriusContract, + erc20StrategyContract, + erc721StrategyContract, + strategyType, + provider, + erc20VotedEvents, + erc721VotedEvents, + executedEvents, + decode, + proposalLoaded, + ); }; }; diff --git a/src/hooks/DAO/loaders/useProposals.ts b/src/hooks/DAO/loaders/useProposals.ts index 0e1c47d046..9e7aaf7d8d 100644 --- a/src/hooks/DAO/loaders/useProposals.ts +++ b/src/hooks/DAO/loaders/useProposals.ts @@ -14,17 +14,17 @@ export const useDAOProposals = () => { } = useFractal(); const { setMethodOnInterval, clearIntervals } = useUpdateTimer(daoAddress); - const azoriusProposals = useAzoriusProposals(); + const loadAzoriusProposals = useAzoriusProposals(); const loadSafeMultisigProposals = useSafeMultisigProposals(); const loadDAOProposals = useCallback(async () => { clearIntervals(); if ( (type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721) && - azoriusProposals !== undefined + loadAzoriusProposals !== undefined ) { // load Azorius proposals and strategies - azoriusProposals.loadAzoriusProposals(proposal => { + loadAzoriusProposals(proposal => { action.dispatch({ type: FractalGovernanceAction.SET_AZORIUS_PROPOSAL, payload: proposal, @@ -37,7 +37,7 @@ export const useDAOProposals = () => { }, [ clearIntervals, type, - azoriusProposals, + loadAzoriusProposals, action, setMethodOnInterval, loadSafeMultisigProposals, From 02adb1a93efe35303f6eb2092b8c046c2588d7fa Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 15:47:20 -0400 Subject: [PATCH 21/26] Code is a flat circle Inline the `loadAzoriusProposals` again so we can use a ref to track the current DAO and stop the sequential processing of proposals if the DAO changes. --- .../loaders/governance/useAzoriusProposals.ts | 143 ++++++++++-------- src/hooks/DAO/loaders/useProposals.ts | 5 +- 2 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index b7066f8130..1e8b61d0f5 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -5,7 +5,7 @@ import { } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/Azorius'; import { VotedEvent as ERC20VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC721Voting'; -import { useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useFractal } from '../../../../providers/App/AppProvider'; import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { ProposalMetadata, VotingStrategyType, DecodedTransaction } from '../../../../types'; @@ -15,61 +15,9 @@ import { mapProposalCreatedEventToProposal, decodeTransactions } from '../../../ import useSafeContracts from '../../../safe/useSafeContracts'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; -const loadAzoriusProposals = async ( - azoriusContract: Azorius, - erc20StrategyContract: LinearERC20Voting | undefined, - erc721StrategyContract: LinearERC721Voting | undefined, - strategyType: VotingStrategyType, - provider: Providers, - erc20VotedEvents: Promise, - erc721VotedEvents: Promise, - executedEvents: Promise, - decode: (value: string, to: string, data?: string | undefined) => Promise, - proposalLoaded: (proposal: AzoriusProposal) => void, -) => { - const proposalCreatedFilter = azoriusContract.filters.ProposalCreated(); - const proposalCreatedEvents = ( - await azoriusContract.queryFilter(proposalCreatedFilter) - ).reverse(); - - for (const proposalCreatedEvent of proposalCreatedEvents) { - let proposalData; - if (proposalCreatedEvent.args.metadata) { - const metadataEvent: ProposalMetadata = JSON.parse(proposalCreatedEvent.args.metadata); - const decodedTransactions = await decodeTransactions( - decode, - proposalCreatedEvent.args.transactions, - ); - proposalData = { - metaData: { - title: metadataEvent.title, - description: metadataEvent.description, - documentationUrl: metadataEvent.documentationUrl, - }, - transactions: proposalCreatedEvent.args.transactions, - decodedTransactions, - }; - } - - const proposal = await mapProposalCreatedEventToProposal( - erc20StrategyContract, - erc721StrategyContract, - strategyType, - proposalCreatedEvent.args.proposalId, - proposalCreatedEvent.args.proposer, - azoriusContract, - provider, - erc20VotedEvents, - erc721VotedEvents, - executedEvents, - proposalData, - ); - - proposalLoaded(proposal); - } -}; - export const useAzoriusProposals = () => { + const currentAzoriusAddress = useRef(); + const { governanceContracts: { azoriusContractAddress, @@ -153,9 +101,86 @@ export const useAzoriusProposals = () => { return events; }, [azoriusContract]); - if (!azoriusContract || !strategyType || !provider) { - return undefined; - } + useEffect(() => { + if (!azoriusContractAddress) { + currentAzoriusAddress.current = undefined; + } + + if (azoriusContractAddress && currentAzoriusAddress.current !== azoriusContractAddress) { + currentAzoriusAddress.current = azoriusContractAddress; + } + }, [azoriusContractAddress]); + + const loadAzoriusProposals = useCallback( + async ( + _azoriusContract: Azorius | undefined, + _erc20StrategyContract: LinearERC20Voting | undefined, + _erc721StrategyContract: LinearERC721Voting | undefined, + _strategyType: VotingStrategyType | undefined, + _erc20VotedEvents: Promise, + _erc721VotedEvents: Promise, + _executedEvents: Promise, + _provider: Providers | undefined, + _decode: ( + value: string, + to: string, + data?: string | undefined, + ) => Promise, + _proposalLoaded: (proposal: AzoriusProposal) => void, + ) => { + if (!_strategyType || !_azoriusContract || !_provider) { + return; + } + + const proposalCreatedFilter = _azoriusContract.filters.ProposalCreated(); + const proposalCreatedEvents = ( + await _azoriusContract.queryFilter(proposalCreatedFilter) + ).reverse(); + + for (const proposalCreatedEvent of proposalCreatedEvents) { + let proposalData; + if (proposalCreatedEvent.args.metadata) { + const metadataEvent: ProposalMetadata = JSON.parse(proposalCreatedEvent.args.metadata); + const decodedTransactions = await decodeTransactions( + _decode, + proposalCreatedEvent.args.transactions, + ); + proposalData = { + metaData: { + title: metadataEvent.title, + description: metadataEvent.description, + documentationUrl: metadataEvent.documentationUrl, + }, + transactions: proposalCreatedEvent.args.transactions, + decodedTransactions, + }; + } + + const proposal = await mapProposalCreatedEventToProposal( + _erc20StrategyContract, + _erc721StrategyContract, + _strategyType, + proposalCreatedEvent.args.proposalId, + proposalCreatedEvent.args.proposer, + _azoriusContract, + _provider, + _erc20VotedEvents, + _erc721VotedEvents, + _executedEvents, + proposalData, + ); + + if (currentAzoriusAddress.current !== azoriusContractAddress) { + // The DAO has changed, don't load the just-fetched proposal, + // into state, and get out of this function completely. + return; + } + + _proposalLoaded(proposal); + } + }, + [azoriusContractAddress], + ); return (proposalLoaded: (proposal: AzoriusProposal) => void) => { return loadAzoriusProposals( @@ -163,10 +188,10 @@ export const useAzoriusProposals = () => { erc20StrategyContract, erc721StrategyContract, strategyType, - provider, erc20VotedEvents, erc721VotedEvents, executedEvents, + provider, decode, proposalLoaded, ); diff --git a/src/hooks/DAO/loaders/useProposals.ts b/src/hooks/DAO/loaders/useProposals.ts index 9e7aaf7d8d..fdc58d6219 100644 --- a/src/hooks/DAO/loaders/useProposals.ts +++ b/src/hooks/DAO/loaders/useProposals.ts @@ -19,10 +19,7 @@ export const useDAOProposals = () => { const loadDAOProposals = useCallback(async () => { clearIntervals(); - if ( - (type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721) && - loadAzoriusProposals !== undefined - ) { + if (type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721) { // load Azorius proposals and strategies loadAzoriusProposals(proposal => { action.dispatch({ From 7d509626588a249d333d8c464bb1f31b7619ee01 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 16:43:01 -0400 Subject: [PATCH 22/26] Remove stray console.log --- src/hooks/DAO/proposal/useSubmitProposal.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index 7daed94f9a..ebe6ef7c59 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -334,7 +334,6 @@ export default function useSubmitProposal() { }), ) ).wait(); - console.log('success'); toast.dismiss(toastId); toast(successToastMessage); if (successCallback) { From 72f74aba46c2b18ee46a2d66856454bb8c13d6b0 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 16:43:24 -0400 Subject: [PATCH 23/26] Delay processing new proposal for a block, to give RPC chance to catch up --- .../DAO/loaders/governance/useAzoriusProposalListeners.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts index 2f0f407451..5b11edd9b8 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts @@ -23,6 +23,7 @@ import { mapProposalCreatedEventToProposal, decodeTransactions, } from '../../../../utils'; +import { getAverageBlockTime } from '../../../../utils/contract'; import useSafeContracts from '../../../safe/useSafeContracts'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; @@ -36,6 +37,13 @@ const proposalCreatedEventListener = ( dispatch: Dispatch, ): TypedListener => { return async (_strategyAddress, proposalId, proposer, transactions, metadata) => { + // Wait for a block before processing. + // We've seen that calling smart contract functions in `mapProposalCreatedEventToProposal` + // which include the `proposalId` error out because the RPC node (rather, the block it's on) + // doesn't see this proposal yet (despite the event being caught in the app...). + const averageBlockTime = await getAverageBlockTime(provider); + await new Promise(resolve => setTimeout(resolve, averageBlockTime * 1000)); + if (!metadata) { return; } From 8df3b3906e75783b4f50d23f1c5096ca84021f24 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 17:16:47 -0400 Subject: [PATCH 24/26] Lint fixes, move listener --- ...salListeners.ts => useAzoriusListeners.ts} | 23 ++++++++++++++++++- .../governance/useERC20LinearStrategy.ts | 20 ---------------- .../governance/useERC721LinearStrategy.ts | 22 ------------------ src/hooks/DAO/loaders/useFractalNode.ts | 2 -- src/hooks/DAO/proposal/useSubmitProposal.ts | 1 - src/hooks/DAO/useDAOController.ts | 4 ++-- 6 files changed, 24 insertions(+), 48 deletions(-) rename src/hooks/DAO/loaders/governance/{useAzoriusProposalListeners.ts => useAzoriusListeners.ts} (91%) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts b/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts similarity index 91% rename from src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts rename to src/hooks/DAO/loaders/governance/useAzoriusListeners.ts index 5b11edd9b8..f8a42c9f76 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposalListeners.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts @@ -26,6 +26,7 @@ import { import { getAverageBlockTime } from '../../../../utils/contract'; import useSafeContracts from '../../../safe/useSafeContracts'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; +import { TimelockPeriodUpdatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/MultisigFreezeGuard'; const proposalCreatedEventListener = ( azoriusContract: Azorius, @@ -133,7 +134,7 @@ const erc721VotedEventListener = ( }; }; -export const useAzoriusProposalListeners = () => { +export const useAzoriusListeners = () => { const { action, governanceContracts: { @@ -249,4 +250,24 @@ export const useAzoriusProposalListeners = () => { erc721StrategyContract.off(votedEvent, listener); }; }, [action.dispatch, erc721StrategyContract, strategyType]); + + useEffect(() => { + if (!azoriusContract) { + return; + } + + const timeLockPeriodFilter = azoriusContract.filters.TimelockPeriodUpdated(); + const timelockPeriodListener: TypedListener = timelockPeriod => { + action.dispatch({ + type: FractalGovernanceAction.UPDATE_TIMELOCK_PERIOD, + payload: BigNumber.from(timelockPeriod), + }); + }; + + azoriusContract.on(timeLockPeriodFilter, timelockPeriodListener); + + return () => { + azoriusContract.off(timeLockPeriodFilter, timelockPeriodListener); + }; + }, [action, azoriusContract]); }; diff --git a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts index 80b2ec4153..bf445bc9ae 100644 --- a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts @@ -109,25 +109,5 @@ export const useERC20LinearStrategy = () => { }; }, [ozLinearVotingContractAddress, action, baseContracts]); - useEffect(() => { - if (!azoriusContractAddress || !baseContracts) { - return; - } - const azoriusContract = - baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - - const timeLockPeriodFilter = azoriusContract.filters.TimelockPeriodUpdated(); - const timelockPeriodListener: TypedListener = timelockPeriod => { - action.dispatch({ - type: FractalGovernanceAction.UPDATE_TIMELOCK_PERIOD, - payload: BigNumber.from(timelockPeriod), - }); - }; - azoriusContract.on(timeLockPeriodFilter, timelockPeriodListener); - return () => { - azoriusContract.off(timeLockPeriodFilter, timelockPeriodListener); - }; - }, [azoriusContractAddress, action, baseContracts]); - return loadERC20Strategy; }; diff --git a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts index 516ab1cccf..257a1fb99b 100644 --- a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts @@ -1,5 +1,4 @@ import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; -import { TimelockPeriodUpdatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/MultisigFreezeGuard'; import { VotingPeriodUpdatedEvent, QuorumThresholdUpdatedEvent, @@ -117,26 +116,5 @@ export const useERC721LinearStrategy = () => { }; }, [erc721LinearVotingContractAddress, action, baseContracts, type]); - // this is duplicated in useERC20LinearStrategy line for line - // useEffect(() => { - // if (!azoriusContractAddress || !baseContracts) { - // return; - // } - // const azoriusContract = - // baseContracts.fractalAzoriusMasterCopyContract.asProvider.attach(azoriusContractAddress); - - // const timeLockPeriodFilter = azoriusContract.filters.TimelockPeriodUpdated(); - // const timelockPeriodListener: TypedListener = timelockPeriod => { - // action.dispatch({ - // type: FractalGovernanceAction.UPDATE_TIMELOCK_PERIOD, - // payload: BigNumber.from(timelockPeriod), - // }); - // }; - // azoriusContract.on(timeLockPeriodFilter, timelockPeriodListener); - // return () => { - // azoriusContract.off(timeLockPeriodFilter, timelockPeriodListener); - // }; - // }, [azoriusContractAddress, action, baseContracts]); - return loadERC721Strategy; }; diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index f6fe59942e..52836b4322 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -8,7 +8,6 @@ import { NodeAction } from '../../../providers/App/node/action'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { Node } from '../../../types'; import { mapChildNodes } from '../../../utils/hierarchy'; -import { useAsyncRetry } from '../../utils/useAsyncRetry'; import { useLazyDAOName } from '../useDAOName'; import { useFractalModules } from './useFractalModules'; @@ -33,7 +32,6 @@ export const useFractalNode = ( const { getDaoName } = useLazyDAOName(); const lookupModules = useFractalModules(); - const { requestWithRetries } = useAsyncRetry(); const formatDAOQuery = useCallback((result: { data?: DAOQueryQuery }, _daoAddress: string) => { if (!result.data) { diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index ebe6ef7c59..fe299b3545 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -22,7 +22,6 @@ import { ProposalMetadata, } from '../../../types'; import { buildSafeApiUrl, getAzoriusModuleFromModules } from '../../../utils'; -import { getAverageBlockTime } from '../../../utils/contract'; import useSafeContracts from '../../safe/useSafeContracts'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; import { useFractalModules } from '../loaders/useFractalModules'; diff --git a/src/hooks/DAO/useDAOController.ts b/src/hooks/DAO/useDAOController.ts index 32a30d5b2a..cf64f02197 100644 --- a/src/hooks/DAO/useDAOController.ts +++ b/src/hooks/DAO/useDAOController.ts @@ -2,7 +2,7 @@ import { utils } from 'ethers'; import { useSearchParams } from 'react-router-dom'; import { useFractal } from '../../providers/App/AppProvider'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; -import { useAzoriusProposalListeners } from './loaders/governance/useAzoriusProposalListeners'; +import { useAzoriusListeners } from './loaders/governance/useAzoriusListeners'; import { useERC20Claim } from './loaders/governance/useERC20Claim'; import { useSnapshotProposals } from './loaders/snapshot/useSnapshotProposals'; import { useFractalFreeze } from './loaders/useFractalFreeze'; @@ -49,7 +49,7 @@ export default function useDAOController() { useFractalTreasury(); useERC20Claim(); useSnapshotProposals(); - useAzoriusProposalListeners(); + useAzoriusListeners(); return { invalidQuery, wrongNetwork, errorLoading }; } From d1db6770690abc443d8ba0df261bb77ff767fd43 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 17:22:07 -0400 Subject: [PATCH 25/26] More unnecessary dependency array params --- .../DAO/loaders/governance/useERC721LinearStrategy.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts index 257a1fb99b..95a14c3a3c 100644 --- a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts @@ -16,7 +16,6 @@ import { useTimeHelpers } from '../../../utils/useTimeHelpers'; export const useERC721LinearStrategy = () => { const { governanceContracts: { erc721LinearVotingContractAddress, azoriusContractAddress }, - governance: { type }, action, } = useFractal(); const provider = useEthersProvider(); @@ -72,7 +71,7 @@ export const useERC721LinearStrategy = () => { ]); useEffect(() => { - if (!erc721LinearVotingContractAddress || !baseContracts || !type) { + if (!erc721LinearVotingContractAddress || !baseContracts) { return; } const erc721LinearVotingContract = @@ -90,10 +89,10 @@ export const useERC721LinearStrategy = () => { return () => { erc721LinearVotingContract.off(votingPeriodfilter, listener); }; - }, [erc721LinearVotingContractAddress, action, baseContracts, type]); + }, [erc721LinearVotingContractAddress, action, baseContracts]); useEffect(() => { - if (!erc721LinearVotingContractAddress || !baseContracts || !type) { + if (!erc721LinearVotingContractAddress || !baseContracts) { return; } const erc721LinearVotingContract = @@ -114,7 +113,7 @@ export const useERC721LinearStrategy = () => { return () => { erc721LinearVotingContract.off(quorumThresholdUpdatedFilter, quorumThresholdUpdatedListener); }; - }, [erc721LinearVotingContractAddress, action, baseContracts, type]); + }, [erc721LinearVotingContractAddress, action, baseContracts]); return loadERC721Strategy; }; From db8823bfe3abb068126f25dac7174601deedfaf2 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 3 Apr 2024 17:30:58 -0400 Subject: [PATCH 26/26] lint fixes --- src/hooks/DAO/loaders/governance/useAzoriusListeners.ts | 2 +- src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts b/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts index f8a42c9f76..291a938e46 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts @@ -1,5 +1,6 @@ import { LinearERC20Voting, LinearERC721Voting } from '@fractal-framework/fractal-contracts'; import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; +import { TimelockPeriodUpdatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/MultisigFreezeGuard'; import { Azorius, ProposalCreatedEvent, @@ -26,7 +27,6 @@ import { import { getAverageBlockTime } from '../../../../utils/contract'; import useSafeContracts from '../../../safe/useSafeContracts'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; -import { TimelockPeriodUpdatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/MultisigFreezeGuard'; const proposalCreatedEventListener = ( azoriusContract: Azorius, diff --git a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts index bf445bc9ae..44824c558a 100644 --- a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts @@ -1,5 +1,4 @@ import { TypedListener } from '@fractal-framework/fractal-contracts/dist/typechain-types/common'; -import { TimelockPeriodUpdatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/MultisigFreezeGuard'; import { QuorumNumeratorUpdatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/BaseQuorumPercent'; import { VotingPeriodUpdatedEvent } from '@fractal-framework/fractal-contracts/dist/typechain-types/contracts/azorius/LinearERC20Voting'; import { BigNumber } from 'ethers';