formatProposal.ts
1 import { 2 Payload, 3 PayloadState, 4 ProposalV3State, 5 VotingMachineProposal, 6 VotingMachineProposalState, 7 } from '@aave/contract-helpers'; 8 import { normalizeBN, valueToBigNumber } from '@aave/math-utils'; 9 import { BigNumber } from 'bignumber.js'; 10 import { 11 EnhancedSubgraphProposal, 12 Proposal, 13 SubgraphProposal, 14 } from 'src/hooks/governance/useProposals'; 15 import { EnhancedPayload } from 'src/services/GovernanceV3Service'; 16 import invariant from 'tiny-invariant'; 17 18 import { isDifferentialReached, isQuorumReached } from '../helpers'; 19 20 export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 21 22 export type ProposalVoteInfo = { 23 forVotes: number; 24 againstVotes: number; 25 forPercent: number; 26 againstPercent: number; 27 quorum: string; 28 quorumReached: boolean; 29 currentDifferential: string; 30 requiredDifferential: string; 31 differentialReached: boolean; 32 isPassing: boolean; 33 }; 34 35 export const getProposalVoteInfo = (proposal: EnhancedSubgraphProposal): ProposalVoteInfo => { 36 const votingConfig = proposal.votingConfig; 37 const quorum = votingConfig.yesThreshold; 38 const quorumReached = isQuorumReached( 39 proposal.votes.forVotes, 40 quorum, 41 proposal.constants.precisionDivider 42 ); 43 44 const forVotesBN = valueToBigNumber(proposal.votes.forVotes); 45 const againstVotesBN = valueToBigNumber(proposal.votes.againstVotes); 46 const currentDifferential = normalizeBN(forVotesBN.minus(againstVotesBN), 18).toString(); 47 48 const requiredDifferential = votingConfig.yesNoDifferential; 49 const differentialReached = isDifferentialReached( 50 proposal.votes.forVotes, 51 proposal.votes.againstVotes, 52 requiredDifferential, 53 proposal.constants.precisionDivider 54 ); 55 56 const allVotes = new BigNumber(proposal.votes.forVotes).plus(proposal.votes.againstVotes); 57 const forPercent = allVotes.gt(0) 58 ? new BigNumber(proposal.votes.forVotes).dividedBy(allVotes).toNumber() 59 : 0; 60 const forVotes = normalizeBN(proposal.votes.forVotes, 18).toNumber(); 61 62 const againstPercent = allVotes.gt(0) 63 ? new BigNumber(proposal.votes.againstVotes).dividedBy(allVotes).toNumber() 64 : 0; 65 const againstVotes = normalizeBN(proposal.votes.againstVotes, 18).toNumber(); 66 67 // getProposalState(proposalData.proposalData, votingMachineData); 68 69 return { 70 forVotes, 71 againstVotes, 72 forPercent, 73 againstPercent, 74 quorum, 75 quorumReached, 76 currentDifferential, 77 requiredDifferential, 78 differentialReached, 79 isPassing: quorumReached && differentialReached, 80 }; 81 }; 82 83 export const getProposalStateTimestamp = (state: ProposalV3State, proposal: Proposal): number => { 84 switch (state) { 85 case ProposalV3State.Null: 86 invariant(false, 'Timestamp of a null proposal can not be accessed'); 87 case ProposalV3State.Created: 88 return Number(proposal.subgraphProposal.transactions.created.timestamp); 89 case ProposalV3State.Active: 90 if (proposal.subgraphProposal.transactions.active) { 91 return Number(proposal.subgraphProposal.transactions.active.timestamp); 92 } 93 return ( 94 Number(proposal.subgraphProposal.transactions.created.timestamp) + 95 Number(proposal.subgraphProposal.votingConfig.cooldownBeforeVotingStart) 96 ); 97 case ProposalV3State.Queued: 98 if (proposal.subgraphProposal.transactions.queued) { 99 return Number(proposal.subgraphProposal.transactions.queued.timestamp); 100 } 101 // special case in case the proposal vote is already finished on voting chain but the result has not been sent to core. 102 if (proposal.subgraphProposal.transactions.active) { 103 // case that voting finished and result was sent to core but core has not processed it yet. 104 if (proposal.votingMachineData.proposalData.votingClosedAndSentTimestamp) { 105 return proposal.votingMachineData.proposalData.votingClosedAndSentTimestamp; 106 } 107 // case that voting is on going or finished and result was not sent to core yet. 108 if (proposal.votingMachineData.proposalData.endTime) { 109 return proposal.votingMachineData.proposalData.endTime; 110 } 111 // case that proposal is active but has not been sent to the voting machine. Special case since proposal.votingDuration gets asigned and locked when proposal is moved to Active. 112 return ( 113 Number(proposal.subgraphProposal.transactions.active.timestamp) + 114 Number(proposal.subgraphProposal.votingDuration) 115 ); 116 } 117 // we know that the proposal is not going to be active but using the recursive function for consistency. 118 return ( 119 getProposalStateTimestamp(ProposalV3State.Active, proposal) + 120 Number(proposal.subgraphProposal.votingConfig.votingDuration) 121 ); 122 case ProposalV3State.Executed: 123 if (proposal.subgraphProposal.transactions.executed) { 124 return Number(proposal.subgraphProposal.transactions.executed.timestamp); 125 } 126 return ( 127 getProposalStateTimestamp(ProposalV3State.Queued, proposal) + 128 Number(proposal.subgraphProposal.constants.cooldownPeriod) 129 ); 130 case ProposalV3State.Failed: 131 if (proposal.subgraphProposal.transactions.failed) { 132 return Number(proposal.subgraphProposal.transactions.failed.timestamp); 133 } 134 return getProposalStateTimestamp(ProposalV3State.Queued, proposal); 135 case ProposalV3State.Cancelled: 136 if (!proposal.subgraphProposal.transactions.canceled) { 137 // since proposal can be cancelled in almost any state this is only useful in the case we know its canceled since we can't infer the timestamp. 138 invariant( 139 false, 140 'Timestamp of a cancelled proposal can only be accessed if the proposal has been cancelled' 141 ); 142 } 143 return Number(proposal.subgraphProposal.transactions.canceled.timestamp); 144 case ProposalV3State.Expired: 145 return Number(proposal.subgraphProposal.constants.expirationTime); 146 default: 147 invariant(false, 'Unknown proposal state'); 148 } 149 }; 150 151 export const getPayloadStateTimestamp = ( 152 payloadState: PayloadState, 153 payload: EnhancedPayload, 154 proposal: Proposal 155 ): number => { 156 switch (payloadState) { 157 case PayloadState.None: 158 invariant(false, 'Timestamp of a null payload can not be accessed'); 159 case PayloadState.Created: 160 return Number(payload.createdAt); 161 case PayloadState.Queued: 162 if (payload.queuedAt !== 0) { 163 return Number(payload.queuedAt); 164 } 165 return getProposalStateTimestamp(ProposalV3State.Executed, proposal); 166 case PayloadState.Executed: 167 if (payload.executedAt) { 168 return Number(payload.executedAt); 169 } 170 return ( 171 getPayloadStateTimestamp(PayloadState.Queued, payload, proposal) + Number(payload.delay) 172 ); 173 case PayloadState.Cancelled: 174 if (payload.cancelledAt) { 175 return Number(payload.cancelledAt); 176 } 177 invariant( 178 false, 179 'Timestamp of a cancelled payload can only be accessed if the payload has been cancelled' 180 ); 181 case PayloadState.Expired: 182 return Number(payload.expirationTime); 183 default: 184 invariant(false, 'Unknown payload state'); 185 } 186 }; 187 188 export const getVotingMachineProposalStateTimestamp = ( 189 votingState: VotingMachineProposalState, 190 proposal: Proposal 191 ): number => { 192 switch (votingState) { 193 case VotingMachineProposalState.NotCreated: 194 invariant(false, 'Timestamp of a not created voting machine state can not be accessed'); 195 case VotingMachineProposalState.Active: 196 if (proposal.votingMachineData.proposalData.startTime !== 0) { 197 return proposal.votingMachineData.proposalData.startTime; 198 } 199 return getProposalStateTimestamp(ProposalV3State.Active, proposal); 200 case VotingMachineProposalState.Finished: 201 if (proposal.votingMachineData.proposalData.endTime !== 0) { 202 return proposal.votingMachineData.proposalData.endTime; 203 } 204 return ( 205 getVotingMachineProposalStateTimestamp(VotingMachineProposalState.Active, proposal) + 206 (Number(proposal.subgraphProposal.votingDuration) || 207 Number(proposal.subgraphProposal.votingConfig.votingDuration)) 208 ); 209 case VotingMachineProposalState.SentToGovernance: 210 if (proposal.votingMachineData.proposalData.votingClosedAndSentTimestamp !== 0) { 211 return proposal.votingMachineData.proposalData.votingClosedAndSentTimestamp; 212 } 213 return getVotingMachineProposalStateTimestamp(VotingMachineProposalState.Finished, proposal); 214 default: 215 invariant(false, 'Unknown voting machine state'); 216 } 217 }; 218 219 export enum ProposalLifecycleStep { 220 Null, 221 Created, 222 OpenForVoting, 223 VotingClosed, 224 Executed, 225 Cancelled, 226 Expired, 227 } 228 229 export const getLifecycleState = ( 230 proposal: SubgraphProposal, 231 votingMachineData: VotingMachineProposal, 232 payloads: Payload[] 233 ) => { 234 const allPayloadsExecuted = payloads.every((payload) => payload.state === PayloadState.Executed); 235 if (proposal.state === ProposalV3State.Cancelled) { 236 return ProposalLifecycleStep.Cancelled; 237 } 238 if (proposal.state === ProposalV3State.Expired) { 239 return ProposalLifecycleStep.Expired; 240 } 241 if ( 242 proposal.state === ProposalV3State.Created || 243 (proposal.state === ProposalV3State.Active && 244 votingMachineData.state === VotingMachineProposalState.NotCreated) 245 ) { 246 return ProposalLifecycleStep.Created; 247 } 248 if (votingMachineData.state === VotingMachineProposalState.Active) { 249 return ProposalLifecycleStep.OpenForVoting; 250 } 251 if ( 252 votingMachineData.state === VotingMachineProposalState.Finished || 253 (votingMachineData.state === VotingMachineProposalState.SentToGovernance && 254 !allPayloadsExecuted) 255 ) { 256 return ProposalLifecycleStep.VotingClosed; 257 } 258 if (allPayloadsExecuted) { 259 return ProposalLifecycleStep.Executed; 260 } 261 invariant(false, 'Could not determine proposal lifecycle state'); 262 }; 263 264 export const getLifecycleStateTimestamp = (state: ProposalLifecycleStep, proposal: Proposal) => { 265 switch (state) { 266 case ProposalLifecycleStep.Created: 267 return getProposalStateTimestamp(ProposalV3State.Created, proposal); 268 case ProposalLifecycleStep.OpenForVoting: 269 return getVotingMachineProposalStateTimestamp(VotingMachineProposalState.Active, proposal); 270 case ProposalLifecycleStep.VotingClosed: 271 return getVotingMachineProposalStateTimestamp(VotingMachineProposalState.Finished, proposal); 272 case ProposalLifecycleStep.Executed: 273 const maxPayloadExecutionTime = Math.max( 274 ...proposal.payloadsData.map((payload) => 275 getPayloadStateTimestamp(PayloadState.Executed, payload, proposal) 276 ) 277 ); 278 return maxPayloadExecutionTime; 279 case ProposalLifecycleStep.Cancelled: 280 return getProposalStateTimestamp(ProposalV3State.Cancelled, proposal); 281 case ProposalLifecycleStep.Expired: 282 return getProposalStateTimestamp(ProposalV3State.Expired, proposal); 283 default: 284 invariant(false, 'Unknown proposal lifecycle state'); 285 } 286 };