/ src / modules / governance / utils / formatProposal.ts
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  };