@gzeoneth/gov-tracker 0.2.1-beta.c266765 → 0.2.1-beta.d575294
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/abis.d.ts +3 -0
- package/dist/abis.d.ts.map +1 -1
- package/dist/abis.js +8 -1
- package/dist/abis.js.map +1 -1
- package/dist/cli/cli.js +88 -44
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/lib/cli.d.ts +41 -3
- package/dist/cli/lib/cli.d.ts.map +1 -1
- package/dist/cli/lib/cli.js +333 -116
- package/dist/cli/lib/cli.js.map +1 -1
- package/dist/cli/lib/json-state.d.ts +23 -0
- package/dist/cli/lib/json-state.d.ts.map +1 -1
- package/dist/cli/lib/json-state.js +51 -4
- package/dist/cli/lib/json-state.js.map +1 -1
- package/dist/constants.d.ts +39 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +47 -2
- package/dist/constants.js.map +1 -1
- package/dist/data/bundled-cache.json +10988 -1580
- package/dist/discovery/governor-discovery.d.ts.map +1 -1
- package/dist/discovery/governor-discovery.js +50 -35
- package/dist/discovery/governor-discovery.js.map +1 -1
- package/dist/discovery/timelock-discovery.d.ts +15 -6
- package/dist/discovery/timelock-discovery.d.ts.map +1 -1
- package/dist/discovery/timelock-discovery.js +27 -11
- package/dist/discovery/timelock-discovery.js.map +1 -1
- package/dist/election/contracts.d.ts +8 -0
- package/dist/election/contracts.d.ts.map +1 -0
- package/dist/election/contracts.js +28 -0
- package/dist/election/contracts.js.map +1 -0
- package/dist/election/details.d.ts +15 -0
- package/dist/election/details.d.ts.map +1 -0
- package/dist/election/details.js +157 -0
- package/dist/election/details.js.map +1 -0
- package/dist/election/index.d.ts +11 -0
- package/dist/election/index.d.ts.map +1 -0
- package/dist/election/index.js +48 -0
- package/dist/election/index.js.map +1 -0
- package/dist/election/params.d.ts +13 -0
- package/dist/election/params.d.ts.map +1 -0
- package/dist/election/params.js +93 -0
- package/dist/election/params.js.map +1 -0
- package/dist/election/participants.d.ts +6 -0
- package/dist/election/participants.d.ts.map +1 -0
- package/dist/election/participants.js +104 -0
- package/dist/election/participants.js.map +1 -0
- package/dist/election/prepare.d.ts +10 -0
- package/dist/election/prepare.d.ts.map +1 -0
- package/dist/election/prepare.js +52 -0
- package/dist/election/prepare.js.map +1 -0
- package/dist/election/proposal-ids.d.ts +18 -0
- package/dist/election/proposal-ids.d.ts.map +1 -0
- package/dist/election/proposal-ids.js +77 -0
- package/dist/election/proposal-ids.js.map +1 -0
- package/dist/election/status.d.ts +15 -0
- package/dist/election/status.d.ts.map +1 -0
- package/dist/election/status.js +102 -0
- package/dist/election/status.js.map +1 -0
- package/dist/election/tracking.d.ts +28 -0
- package/dist/election/tracking.d.ts.map +1 -0
- package/dist/election/tracking.js +412 -0
- package/dist/election/tracking.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -11
- package/dist/index.js.map +1 -1
- package/dist/stages/l2-to-l1-message.d.ts +3 -1
- package/dist/stages/l2-to-l1-message.d.ts.map +1 -1
- package/dist/stages/l2-to-l1-message.js +6 -6
- package/dist/stages/l2-to-l1-message.js.map +1 -1
- package/dist/stages/proposal-created.d.ts +1 -0
- package/dist/stages/proposal-created.d.ts.map +1 -1
- package/dist/stages/proposal-created.js +1 -0
- package/dist/stages/proposal-created.js.map +1 -1
- package/dist/stages/proposal-queued.d.ts +1 -0
- package/dist/stages/proposal-queued.d.ts.map +1 -1
- package/dist/stages/proposal-queued.js +3 -1
- package/dist/stages/proposal-queued.js.map +1 -1
- package/dist/stages/retryables.js +2 -2
- package/dist/stages/retryables.js.map +1 -1
- package/dist/stages/timelock.d.ts +3 -1
- package/dist/stages/timelock.d.ts.map +1 -1
- package/dist/stages/timelock.js +12 -12
- package/dist/stages/timelock.js.map +1 -1
- package/dist/stages/utils.d.ts +5 -9
- package/dist/stages/utils.d.ts.map +1 -1
- package/dist/stages/utils.js +34 -29
- package/dist/stages/utils.js.map +1 -1
- package/dist/tracker/cache.d.ts +10 -6
- package/dist/tracker/cache.d.ts.map +1 -1
- package/dist/tracker/cache.js +36 -13
- package/dist/tracker/cache.js.map +1 -1
- package/dist/tracker/checkpoint-helpers.d.ts +81 -0
- package/dist/tracker/checkpoint-helpers.d.ts.map +1 -0
- package/dist/tracker/checkpoint-helpers.js +201 -0
- package/dist/tracker/checkpoint-helpers.js.map +1 -0
- package/dist/tracker/discovery.d.ts +1 -1
- package/dist/tracker/discovery.d.ts.map +1 -1
- package/dist/tracker/discovery.js +11 -3
- package/dist/tracker/discovery.js.map +1 -1
- package/dist/tracker/execute.d.ts.map +1 -1
- package/dist/tracker/execute.js +1 -25
- package/dist/tracker/execute.js.map +1 -1
- package/dist/tracker/pipeline.d.ts.map +1 -1
- package/dist/tracker/pipeline.js +26 -11
- package/dist/tracker/pipeline.js.map +1 -1
- package/dist/tracker/query.d.ts +1 -0
- package/dist/tracker/query.d.ts.map +1 -1
- package/dist/tracker/query.js +14 -61
- package/dist/tracker/query.js.map +1 -1
- package/dist/tracker/state.d.ts +0 -10
- package/dist/tracker/state.d.ts.map +1 -1
- package/dist/tracker/state.js +1 -28
- package/dist/tracker/state.js.map +1 -1
- package/dist/tracker.d.ts +79 -7
- package/dist/tracker.d.ts.map +1 -1
- package/dist/tracker.js +357 -43
- package/dist/tracker.js.map +1 -1
- package/dist/types/config.d.ts +49 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/core.d.ts +3 -1
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/election.d.ts +66 -11
- package/dist/types/election.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/stages.d.ts +66 -7
- package/dist/types/stages.d.ts.map +1 -1
- package/dist/types/stages.js.map +1 -1
- package/dist/types/tracking.d.ts +10 -10
- package/dist/types/tracking.d.ts.map +1 -1
- package/dist/utils/block-cache.d.ts +50 -0
- package/dist/utils/block-cache.d.ts.map +1 -0
- package/dist/utils/block-cache.js +80 -0
- package/dist/utils/block-cache.js.map +1 -0
- package/dist/utils/formatters.d.ts +91 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +327 -0
- package/dist/utils/formatters.js.map +1 -0
- package/dist/utils/multicall.d.ts +52 -0
- package/dist/utils/multicall.d.ts.map +1 -0
- package/dist/utils/multicall.js +75 -0
- package/dist/utils/multicall.js.map +1 -0
- package/dist/utils/rpc-utils.d.ts +7 -1
- package/dist/utils/rpc-utils.d.ts.map +1 -1
- package/dist/utils/rpc-utils.js +42 -29
- package/dist/utils/rpc-utils.js.map +1 -1
- package/dist/utils/salt-computation.d.ts.map +1 -1
- package/dist/utils/salt-computation.js +33 -7
- package/dist/utils/salt-computation.js.map +1 -1
- package/dist/utils/sanitize.d.ts.map +1 -1
- package/dist/utils/sanitize.js +3 -0
- package/dist/utils/sanitize.js.map +1 -1
- package/dist/utils/stage-metadata.d.ts +0 -20
- package/dist/utils/stage-metadata.d.ts.map +1 -1
- package/dist/utils/stage-metadata.js +29 -44
- package/dist/utils/stage-metadata.js.map +1 -1
- package/dist/utils/timing.d.ts.map +1 -1
- package/dist/utils/timing.js +2 -1
- package/dist/utils/timing.js.map +1 -1
- package/package.json +3 -2
- package/dist/election.d.ts +0 -312
- package/dist/election.d.ts.map +0 -1
- package/dist/election.js +0 -884
- package/dist/election.js.map +0 -1
- package/dist/types/cross-chain.d.ts +0 -24
- package/dist/types/cross-chain.d.ts.map +0 -1
- package/dist/types/cross-chain.js +0 -6
- package/dist/types/cross-chain.js.map +0 -1
package/dist/election.js
DELETED
|
@@ -1,884 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Security Council Election Tracking
|
|
4
|
-
*
|
|
5
|
-
* Provides functions to check election status and prepare election creation transactions.
|
|
6
|
-
* Based on SecurityCouncilElectionTracker from governance repo.
|
|
7
|
-
*
|
|
8
|
-
* @module election
|
|
9
|
-
*/
|
|
10
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.checkElectionStatus = checkElectionStatus;
|
|
12
|
-
exports.prepareElectionCreation = prepareElectionCreation;
|
|
13
|
-
exports.hasVettingPeriod = hasVettingPeriod;
|
|
14
|
-
exports.trackElectionProposal = trackElectionProposal;
|
|
15
|
-
exports.getElectionProposalId = getElectionProposalId;
|
|
16
|
-
exports.getMemberElectionProposalId = getMemberElectionProposalId;
|
|
17
|
-
exports.getElectionProposalParams = getElectionProposalParams;
|
|
18
|
-
exports.prepareMemberElectionTrigger = prepareMemberElectionTrigger;
|
|
19
|
-
exports.getMemberElectionProposalParams = getMemberElectionProposalParams;
|
|
20
|
-
exports.prepareMemberElectionExecution = prepareMemberElectionExecution;
|
|
21
|
-
exports.getElectionIndexForProposalId = getElectionIndexForProposalId;
|
|
22
|
-
exports.trackAllElections = trackAllElections;
|
|
23
|
-
exports.trackIncompleteElections = trackIncompleteElections;
|
|
24
|
-
exports.getContenders = getContenders;
|
|
25
|
-
exports.getNomineesWithVotes = getNomineesWithVotes;
|
|
26
|
-
exports.getExcludedNominees = getExcludedNominees;
|
|
27
|
-
exports.getNomineeElectionDetails = getNomineeElectionDetails;
|
|
28
|
-
exports.getMemberElectionDetails = getMemberElectionDetails;
|
|
29
|
-
const ethers_1 = require("ethers");
|
|
30
|
-
const constants_1 = require("./constants");
|
|
31
|
-
const rpc_utils_1 = require("./utils/rpc-utils");
|
|
32
|
-
const timing_1 = require("./utils/timing");
|
|
33
|
-
const salt_computation_1 = require("./utils/salt-computation");
|
|
34
|
-
const logger_1 = require("./utils/logger");
|
|
35
|
-
const log = logger_1.loggers.election;
|
|
36
|
-
const abis_1 = require("./abis");
|
|
37
|
-
// Helper functions
|
|
38
|
-
/** Create a nominee election governor contract instance */
|
|
39
|
-
function getNomineeGovernor(address = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR, provider) {
|
|
40
|
-
return new ethers_1.ethers.Contract(address, abis_1.NOMINEE_ELECTION_GOVERNOR_ABI, provider);
|
|
41
|
-
}
|
|
42
|
-
/** Create a member election governor contract instance */
|
|
43
|
-
function getMemberGovernor(address = constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR, provider) {
|
|
44
|
-
return new ethers_1.ethers.Contract(address, abis_1.MEMBER_ELECTION_GOVERNOR_ABI, provider);
|
|
45
|
-
}
|
|
46
|
-
/** Get block range for log queries based on proposal snapshot */
|
|
47
|
-
async function getLogQueryBlockRange(governor, proposalId, provider, offsetFromSnapshot = 1000, fallbackRange = 100000) {
|
|
48
|
-
const toBlock = await provider.getBlockNumber();
|
|
49
|
-
let fromBlock;
|
|
50
|
-
try {
|
|
51
|
-
const snapshot = await governor.proposalSnapshot(proposalId);
|
|
52
|
-
fromBlock = Math.max(0, snapshot.toNumber() - offsetFromSnapshot);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
fromBlock = Math.max(0, toBlock - fallbackRange);
|
|
56
|
-
}
|
|
57
|
-
return { fromBlock, toBlock };
|
|
58
|
-
}
|
|
59
|
-
// Core Functions
|
|
60
|
-
/**
|
|
61
|
-
* Check the status of the Security Council election
|
|
62
|
-
*
|
|
63
|
-
* Determines if a new election can be created and when the next one is scheduled.
|
|
64
|
-
*
|
|
65
|
-
* @param l2Provider - Arbitrum One provider
|
|
66
|
-
* @param l1Provider - Ethereum mainnet provider (for L1 timestamp)
|
|
67
|
-
* @param nomineeGovernorAddress - Optional override for nominee election governor address
|
|
68
|
-
* @returns Election status information
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```typescript
|
|
72
|
-
* const status = await checkElectionStatus(l2Provider, l1Provider);
|
|
73
|
-
*
|
|
74
|
-
* if (status.canCreateElection) {
|
|
75
|
-
* console.log("Election ready to create!");
|
|
76
|
-
* } else {
|
|
77
|
-
* console.log(`Next election in ${status.timeUntilElection}`);
|
|
78
|
-
* }
|
|
79
|
-
* ```
|
|
80
|
-
*/
|
|
81
|
-
async function checkElectionStatus(l2Provider, l1Provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
82
|
-
log("checkElectionStatus for %s", nomineeGovernorAddress);
|
|
83
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress, l2Provider);
|
|
84
|
-
// Get L1 block number as seen from L2
|
|
85
|
-
const l1BlockNumber = await (0, timing_1.getL1BlockNumberFromL2)(l2Provider);
|
|
86
|
-
log("L1 block number from L2: %s", l1BlockNumber.toString());
|
|
87
|
-
// Get L1 timestamp for that block
|
|
88
|
-
const l1Block = await (0, rpc_utils_1.queryWithRetry)(() => l1Provider.getBlock(l1BlockNumber.toNumber()));
|
|
89
|
-
if (!l1Block) {
|
|
90
|
-
throw new Error(`L1 block ${l1BlockNumber.toString()} not found. ` +
|
|
91
|
-
`If using a fork, ensure L1 is forked at a block >= ${l1BlockNumber.toString()}`);
|
|
92
|
-
}
|
|
93
|
-
const currentL1Timestamp = l1Block.timestamp;
|
|
94
|
-
log("L1 timestamp: %d", currentL1Timestamp);
|
|
95
|
-
// Get election count, cohort, and next election timestamp
|
|
96
|
-
const electionCount = await (0, rpc_utils_1.queryWithRetry)(() => governor.electionCount());
|
|
97
|
-
const [nextElectionTimestamp, cohort] = await Promise.all([
|
|
98
|
-
(0, rpc_utils_1.queryWithRetry)(() => governor.electionToTimestamp(electionCount)),
|
|
99
|
-
(0, rpc_utils_1.queryWithRetry)(() => governor.electionIndexToCohort(electionCount)),
|
|
100
|
-
]);
|
|
101
|
-
log("electionCount=%s nextTimestamp=%s cohort=%d", electionCount.toString(), nextElectionTimestamp.toString(), cohort);
|
|
102
|
-
const secondsUntilElection = Math.max(0, nextElectionTimestamp.toNumber() - currentL1Timestamp);
|
|
103
|
-
const canCreateElection = secondsUntilElection === 0;
|
|
104
|
-
return {
|
|
105
|
-
electionCount: electionCount.toNumber(),
|
|
106
|
-
cohort: cohort,
|
|
107
|
-
nextElectionTimestamp: nextElectionTimestamp.toNumber(),
|
|
108
|
-
currentL1Timestamp,
|
|
109
|
-
canCreateElection,
|
|
110
|
-
secondsUntilElection,
|
|
111
|
-
timeUntilElection: formatDuration(secondsUntilElection),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Prepare a transaction to create a new Security Council election
|
|
116
|
-
*
|
|
117
|
-
* Only call this if checkElectionStatus indicates canCreateElection is true.
|
|
118
|
-
*
|
|
119
|
-
* @param electionStatus - Status from checkElectionStatus (provides current election count)
|
|
120
|
-
* @param nomineeGovernorAddress - Optional override for nominee election governor address
|
|
121
|
-
* @returns Prepared transaction for creating the election
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```typescript
|
|
125
|
-
* const status = await checkElectionStatus(l2Provider, l1Provider);
|
|
126
|
-
*
|
|
127
|
-
* if (status.canCreateElection) {
|
|
128
|
-
* const { transaction, electionIndex } = prepareElectionCreation(status);
|
|
129
|
-
* console.log(`Creating election #${electionIndex}`);
|
|
130
|
-
* // Execute with your signer
|
|
131
|
-
* const tx = await signer.sendTransaction({
|
|
132
|
-
* to: transaction.to,
|
|
133
|
-
* data: transaction.data,
|
|
134
|
-
* });
|
|
135
|
-
* await tx.wait();
|
|
136
|
-
* }
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
|
-
function prepareElectionCreation(electionStatus, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
140
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress);
|
|
141
|
-
const calldata = governor.interface.encodeFunctionData("createElection", []);
|
|
142
|
-
return {
|
|
143
|
-
transaction: {
|
|
144
|
-
to: nomineeGovernorAddress,
|
|
145
|
-
data: calldata,
|
|
146
|
-
value: "0",
|
|
147
|
-
chain: "arb1",
|
|
148
|
-
chainId: 42161,
|
|
149
|
-
description: `createElection() on SecurityCouncilNomineeElectionGovernor for election #${electionStatus.electionCount}`,
|
|
150
|
-
},
|
|
151
|
-
electionIndex: electionStatus.electionCount,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Check if a governor has a vetting period (is a nominee election governor)
|
|
156
|
-
*
|
|
157
|
-
* @param governorAddress - Address of the governor to check
|
|
158
|
-
* @param provider - Provider for the chain the governor is on
|
|
159
|
-
* @returns True if the governor has a vetting period
|
|
160
|
-
*/
|
|
161
|
-
async function hasVettingPeriod(governorAddress, provider) {
|
|
162
|
-
const governor = getNomineeGovernor(governorAddress, provider);
|
|
163
|
-
try {
|
|
164
|
-
await governor.nomineeVetter();
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// Helpers
|
|
172
|
-
/**
|
|
173
|
-
* Format a duration in seconds to a human-readable string
|
|
174
|
-
*/
|
|
175
|
-
function formatDuration(seconds) {
|
|
176
|
-
if (seconds <= 0)
|
|
177
|
-
return "now";
|
|
178
|
-
// Ensure we're working with integers for clean display
|
|
179
|
-
const totalSeconds = Math.floor(seconds);
|
|
180
|
-
const days = Math.floor(totalSeconds / 86400);
|
|
181
|
-
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
182
|
-
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
183
|
-
const secs = totalSeconds % 60;
|
|
184
|
-
const parts = [];
|
|
185
|
-
if (days > 0)
|
|
186
|
-
parts.push(`${days}d`);
|
|
187
|
-
if (hours > 0)
|
|
188
|
-
parts.push(`${hours}h`);
|
|
189
|
-
if (minutes > 0)
|
|
190
|
-
parts.push(`${minutes}m`);
|
|
191
|
-
if (secs > 0 && parts.length < 2)
|
|
192
|
-
parts.push(`${secs}s`);
|
|
193
|
-
return parts.join(" ") || "0s";
|
|
194
|
-
}
|
|
195
|
-
const PROPOSAL_STATES = [
|
|
196
|
-
"Pending",
|
|
197
|
-
"Active",
|
|
198
|
-
"Canceled",
|
|
199
|
-
"Defeated",
|
|
200
|
-
"Succeeded",
|
|
201
|
-
"Queued",
|
|
202
|
-
"Expired",
|
|
203
|
-
"Executed",
|
|
204
|
-
];
|
|
205
|
-
function stateToString(state) {
|
|
206
|
-
const result = PROPOSAL_STATES[state];
|
|
207
|
-
if (!result) {
|
|
208
|
-
throw new Error(`Unknown proposal state number: ${state}`);
|
|
209
|
-
}
|
|
210
|
-
return result;
|
|
211
|
-
}
|
|
212
|
-
function determineElectionPhase(nomineeProposalState, memberProposalId, memberProposalState, isInVettingPeriod) {
|
|
213
|
-
if (memberProposalState === "Executed") {
|
|
214
|
-
return "COMPLETED";
|
|
215
|
-
}
|
|
216
|
-
if (memberProposalId) {
|
|
217
|
-
if (memberProposalState === "Succeeded" || memberProposalState === "Queued") {
|
|
218
|
-
return "PENDING_EXECUTION";
|
|
219
|
-
}
|
|
220
|
-
return "MEMBER_ELECTION";
|
|
221
|
-
}
|
|
222
|
-
if (nomineeProposalState === "Executed") {
|
|
223
|
-
return "PENDING_EXECUTION";
|
|
224
|
-
}
|
|
225
|
-
if (isInVettingPeriod) {
|
|
226
|
-
return "VETTING_PERIOD";
|
|
227
|
-
}
|
|
228
|
-
if (nomineeProposalState === "Active" || nomineeProposalState === "Pending") {
|
|
229
|
-
return "NOMINEE_SELECTION";
|
|
230
|
-
}
|
|
231
|
-
if (nomineeProposalState === "Succeeded") {
|
|
232
|
-
return "PENDING_EXECUTION";
|
|
233
|
-
}
|
|
234
|
-
return "NOT_STARTED";
|
|
235
|
-
}
|
|
236
|
-
// Election Proposal Tracking
|
|
237
|
-
/**
|
|
238
|
-
* Track the status of a Security Council election by its index
|
|
239
|
-
*
|
|
240
|
-
* This function provides detailed tracking of an election's progress through
|
|
241
|
-
* the nominee selection, vetting, and member election phases.
|
|
242
|
-
*
|
|
243
|
-
* @param electionIndex - The election index to track
|
|
244
|
-
* @param l2Provider - Arbitrum One provider
|
|
245
|
-
* @param l1Provider - Ethereum mainnet provider (for L1 block number)
|
|
246
|
-
* @param options - Optional address overrides
|
|
247
|
-
* @returns Detailed election proposal status
|
|
248
|
-
*
|
|
249
|
-
* @example
|
|
250
|
-
* ```typescript
|
|
251
|
-
* const status = await trackElectionProposal(
|
|
252
|
-
* 5, // election index
|
|
253
|
-
* l2Provider,
|
|
254
|
-
* l1Provider
|
|
255
|
-
* );
|
|
256
|
-
*
|
|
257
|
-
* if (status.phase === "VETTING_PERIOD") {
|
|
258
|
-
* console.log(`${status.compliantNomineeCount}/6 nominees compliant`);
|
|
259
|
-
* }
|
|
260
|
-
* ```
|
|
261
|
-
*/
|
|
262
|
-
async function trackElectionProposal(electionIndex, l2Provider, _l1Provider, options = {}) {
|
|
263
|
-
const nomineeGovernorAddress = options.nomineeGovernorAddress ?? constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR;
|
|
264
|
-
const memberGovernorAddress = options.memberGovernorAddress ?? constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR;
|
|
265
|
-
log("trackElectionProposal for index %d", electionIndex);
|
|
266
|
-
const nomineeGovernor = getNomineeGovernor(nomineeGovernorAddress, l2Provider);
|
|
267
|
-
const memberGovernor = getMemberGovernor(memberGovernorAddress, l2Provider);
|
|
268
|
-
// Get cohort for this election
|
|
269
|
-
const cohort = (await (0, rpc_utils_1.queryWithRetry)(() => nomineeGovernor.electionIndexToCohort(electionIndex)));
|
|
270
|
-
// Get nominee proposal ID for this election
|
|
271
|
-
const nomineeProposalId = await getElectionProposalId(electionIndex, l2Provider, nomineeGovernorAddress);
|
|
272
|
-
// If no nominee proposal, election hasn't started
|
|
273
|
-
if (!nomineeProposalId) {
|
|
274
|
-
return {
|
|
275
|
-
electionIndex,
|
|
276
|
-
phase: "NOT_STARTED",
|
|
277
|
-
cohort,
|
|
278
|
-
nomineeProposalId: null,
|
|
279
|
-
memberProposalId: null,
|
|
280
|
-
nomineeProposalState: null,
|
|
281
|
-
memberProposalState: null,
|
|
282
|
-
compliantNomineeCount: 0,
|
|
283
|
-
targetNomineeCount: constants_1.TIMING.SECURITY_COUNCIL_TARGET_NOMINEES,
|
|
284
|
-
vettingDeadline: null,
|
|
285
|
-
isInVettingPeriod: false,
|
|
286
|
-
canProceedToMemberPhase: false,
|
|
287
|
-
canExecuteMember: false,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
// Get nominee proposal state
|
|
291
|
-
const nomineeState = await (0, rpc_utils_1.queryWithRetry)(() => nomineeGovernor.state(nomineeProposalId));
|
|
292
|
-
const nomineeProposalState = stateToString(nomineeState);
|
|
293
|
-
// Get vetting deadline
|
|
294
|
-
const vettingDeadlineBN = await (0, rpc_utils_1.queryWithRetry)(() => nomineeGovernor.proposalVettingDeadline(nomineeProposalId));
|
|
295
|
-
const vettingDeadline = vettingDeadlineBN.toNumber();
|
|
296
|
-
// Get current L1 block number
|
|
297
|
-
const currentL1Block = await (0, timing_1.getL1BlockNumberFromL2)(l2Provider);
|
|
298
|
-
// Determine if in vetting period
|
|
299
|
-
const isInVettingPeriod = nomineeProposalState === "Succeeded" && currentL1Block.lte(vettingDeadlineBN);
|
|
300
|
-
// Get compliant nominee count
|
|
301
|
-
let compliantNomineeCount = 0;
|
|
302
|
-
try {
|
|
303
|
-
const count = await (0, rpc_utils_1.queryWithRetry)(() => nomineeGovernor.compliantNomineeCount(nomineeProposalId));
|
|
304
|
-
compliantNomineeCount = count.toNumber();
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
// May fail if no nominees yet
|
|
308
|
-
}
|
|
309
|
-
// Check for member proposal using computed proposal ID (same scheme as nominee governor)
|
|
310
|
-
let memberProposalId = null;
|
|
311
|
-
let memberProposalState = null;
|
|
312
|
-
// Compute member proposal ID using getProposeArgs + hashProposal (same as nominee)
|
|
313
|
-
const computedMemberProposalId = await computeElectionProposalId(electionIndex, memberGovernor);
|
|
314
|
-
try {
|
|
315
|
-
// Don't use queryWithRetry - we expect this to revert for non-existent proposals
|
|
316
|
-
const memberState = await memberGovernor.state(computedMemberProposalId);
|
|
317
|
-
// If we get here without reverting, the proposal exists
|
|
318
|
-
memberProposalId = computedMemberProposalId;
|
|
319
|
-
memberProposalState = stateToString(memberState);
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
// Member election not yet created (state() reverts for non-existent proposals)
|
|
323
|
-
}
|
|
324
|
-
const phase = determineElectionPhase(nomineeProposalState, memberProposalId, memberProposalState, isInVettingPeriod);
|
|
325
|
-
const canProceedToMemberPhase = nomineeProposalState === "Succeeded" &&
|
|
326
|
-
!isInVettingPeriod &&
|
|
327
|
-
compliantNomineeCount >= constants_1.TIMING.SECURITY_COUNCIL_TARGET_NOMINEES &&
|
|
328
|
-
!memberProposalId;
|
|
329
|
-
const canExecuteMember = memberProposalState === "Succeeded";
|
|
330
|
-
return {
|
|
331
|
-
electionIndex,
|
|
332
|
-
phase,
|
|
333
|
-
cohort,
|
|
334
|
-
nomineeProposalId,
|
|
335
|
-
memberProposalId,
|
|
336
|
-
nomineeProposalState,
|
|
337
|
-
memberProposalState,
|
|
338
|
-
compliantNomineeCount,
|
|
339
|
-
targetNomineeCount: constants_1.TIMING.SECURITY_COUNCIL_TARGET_NOMINEES,
|
|
340
|
-
vettingDeadline,
|
|
341
|
-
isInVettingPeriod,
|
|
342
|
-
canProceedToMemberPhase,
|
|
343
|
-
canExecuteMember,
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Get the proposal ID for a given election index from any election governor
|
|
348
|
-
*
|
|
349
|
-
* Uses getProposeArgs to get proposal parameters and hashProposal to calculate the proposal ID.
|
|
350
|
-
* Both nominee and member governors use the same proposal ID scheme via ElectionGovernor base.
|
|
351
|
-
*
|
|
352
|
-
* @param electionIndex - Election index
|
|
353
|
-
* @param governor - Election governor contract instance
|
|
354
|
-
* @returns Proposal ID as string
|
|
355
|
-
*/
|
|
356
|
-
async function computeElectionProposalId(electionIndex, governor) {
|
|
357
|
-
// getProposeArgs returns: [targets, values, calldatas, description]
|
|
358
|
-
const proposeArgs = (await (0, rpc_utils_1.queryWithRetry)(() => governor.getProposeArgs(electionIndex)));
|
|
359
|
-
const [targets, values, calldatas, description] = proposeArgs;
|
|
360
|
-
// Hash the description to get descriptionHash
|
|
361
|
-
const descriptionHash = (0, salt_computation_1.saltFromDescription)(description);
|
|
362
|
-
// Calculate proposal ID using hashProposal
|
|
363
|
-
const proposalId = await (0, rpc_utils_1.queryWithRetry)(() => governor.hashProposal(targets, values, calldatas, descriptionHash));
|
|
364
|
-
return ethers_1.BigNumber.from(proposalId).toString();
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Get the proposal ID for a given election index
|
|
368
|
-
*
|
|
369
|
-
* Uses getProposeArgs to get proposal parameters and hashProposal to calculate the proposal ID.
|
|
370
|
-
* Verifies the proposal exists by checking state() - returns null if proposal doesn't exist.
|
|
371
|
-
*
|
|
372
|
-
* @param electionIndex - Election index
|
|
373
|
-
* @param provider - L2 provider
|
|
374
|
-
* @param nomineeGovernorAddress - Optional governor address override
|
|
375
|
-
* @returns Proposal ID or null if election not yet created
|
|
376
|
-
*/
|
|
377
|
-
async function getElectionProposalId(electionIndex, provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
378
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress, provider);
|
|
379
|
-
const proposalId = await computeElectionProposalId(electionIndex, governor);
|
|
380
|
-
// Verify the proposal exists by checking state() - reverts for non-existent proposals
|
|
381
|
-
try {
|
|
382
|
-
await governor.state(proposalId);
|
|
383
|
-
return proposalId;
|
|
384
|
-
}
|
|
385
|
-
catch {
|
|
386
|
-
// Proposal doesn't exist (state() reverts for unknown proposal IDs)
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Get the member election proposal ID for a given election index
|
|
392
|
-
*
|
|
393
|
-
* @param electionIndex - Election index
|
|
394
|
-
* @param provider - L2 provider
|
|
395
|
-
* @param memberGovernorAddress - Optional governor address override
|
|
396
|
-
* @returns Member proposal ID as string
|
|
397
|
-
*/
|
|
398
|
-
async function getMemberElectionProposalId(electionIndex, provider, memberGovernorAddress = constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR) {
|
|
399
|
-
const governor = getMemberGovernor(memberGovernorAddress, provider);
|
|
400
|
-
return computeElectionProposalId(electionIndex, governor);
|
|
401
|
-
}
|
|
402
|
-
// Member Election Trigger Functions
|
|
403
|
-
/**
|
|
404
|
-
* Search for ProposalCreated event and extract proposal parameters
|
|
405
|
-
*
|
|
406
|
-
* Common helper used by both nominee and member election param lookups.
|
|
407
|
-
*/
|
|
408
|
-
async function findProposalCreatedParams(proposalId, governorAddress, governor, provider) {
|
|
409
|
-
const topic = abis_1.proposalCreatedInterface.getEventTopic("ProposalCreated");
|
|
410
|
-
let startBlock;
|
|
411
|
-
try {
|
|
412
|
-
const snapshot = await governor.proposalSnapshot(proposalId);
|
|
413
|
-
startBlock = Math.max(0, snapshot.toNumber() - 1000);
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
const currentBlock = await provider.getBlockNumber();
|
|
417
|
-
startBlock = Math.max(0, currentBlock - 10000);
|
|
418
|
-
}
|
|
419
|
-
const currentBlock = await provider.getBlockNumber();
|
|
420
|
-
const logs = await (0, rpc_utils_1.queryWithRetry)(() => provider.getLogs({
|
|
421
|
-
address: governorAddress,
|
|
422
|
-
topics: [topic],
|
|
423
|
-
fromBlock: startBlock,
|
|
424
|
-
toBlock: currentBlock,
|
|
425
|
-
}));
|
|
426
|
-
for (const eventLog of logs) {
|
|
427
|
-
try {
|
|
428
|
-
const parsed = abis_1.proposalCreatedInterface.parseLog(eventLog);
|
|
429
|
-
const args = parsed.args;
|
|
430
|
-
if (args.proposalId.toString() === proposalId) {
|
|
431
|
-
log("Found ProposalCreated event for proposal %s", proposalId);
|
|
432
|
-
return {
|
|
433
|
-
targets: args.targets,
|
|
434
|
-
values: args.values,
|
|
435
|
-
calldatas: args.calldatas,
|
|
436
|
-
description: args.description,
|
|
437
|
-
descriptionHash: (0, salt_computation_1.saltFromDescription)(args.description),
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
log("ProposalCreated event not found for proposal %s", proposalId);
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Build a prepared execute() transaction from proposal params
|
|
450
|
-
*/
|
|
451
|
-
function buildExecuteTransaction(params, governorAddress, description) {
|
|
452
|
-
const calldata = abis_1.governorInterface.encodeFunctionData("execute", [
|
|
453
|
-
params.targets,
|
|
454
|
-
params.values,
|
|
455
|
-
params.calldatas,
|
|
456
|
-
params.descriptionHash,
|
|
457
|
-
]);
|
|
458
|
-
return {
|
|
459
|
-
to: governorAddress,
|
|
460
|
-
data: calldata,
|
|
461
|
-
value: "0",
|
|
462
|
-
chain: "arb1",
|
|
463
|
-
chainId: 42161,
|
|
464
|
-
description,
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Get the proposal parameters for an election proposal
|
|
469
|
-
*
|
|
470
|
-
* Searches for the ProposalCreated event to extract targets, values, calldatas,
|
|
471
|
-
* and description needed for execute() call.
|
|
472
|
-
*
|
|
473
|
-
* @param electionIndex - Election index
|
|
474
|
-
* @param provider - L2 provider
|
|
475
|
-
* @param nomineeGovernorAddress - Optional governor address override
|
|
476
|
-
* @returns Election proposal params or null if not found
|
|
477
|
-
*/
|
|
478
|
-
async function getElectionProposalParams(electionIndex, provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
479
|
-
log("getElectionProposalParams for index %d", electionIndex);
|
|
480
|
-
const proposalId = await getElectionProposalId(electionIndex, provider, nomineeGovernorAddress);
|
|
481
|
-
if (!proposalId) {
|
|
482
|
-
log("No proposal ID found for election %d", electionIndex);
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress, provider);
|
|
486
|
-
return findProposalCreatedParams(proposalId, nomineeGovernorAddress, governor, provider);
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Prepare a transaction to trigger member election creation
|
|
490
|
-
*
|
|
491
|
-
* After the vetting period ends with 6+ compliant nominees, calling execute()
|
|
492
|
-
* on the NomineeElectionGovernor creates the member election proposal.
|
|
493
|
-
*
|
|
494
|
-
* @param electionStatus - Status from trackElectionProposal (must have canProceedToMemberPhase=true)
|
|
495
|
-
* @param provider - L2 provider
|
|
496
|
-
* @param nomineeGovernorAddress - Optional governor address override
|
|
497
|
-
* @returns Prepared transaction or null if not ready
|
|
498
|
-
*
|
|
499
|
-
* @example
|
|
500
|
-
* ```typescript
|
|
501
|
-
* const status = await trackElectionProposal(5, l2Provider, l1Provider);
|
|
502
|
-
*
|
|
503
|
-
* if (status.canProceedToMemberPhase) {
|
|
504
|
-
* const prepared = await prepareMemberElectionTrigger(status, l2Provider);
|
|
505
|
-
* if (prepared) {
|
|
506
|
-
* const tx = await signer.sendTransaction({
|
|
507
|
-
* to: prepared.to,
|
|
508
|
-
* data: prepared.data,
|
|
509
|
-
* });
|
|
510
|
-
* await tx.wait();
|
|
511
|
-
* console.log("Member election created!");
|
|
512
|
-
* }
|
|
513
|
-
* }
|
|
514
|
-
* ```
|
|
515
|
-
*/
|
|
516
|
-
async function prepareMemberElectionTrigger(electionStatus, provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
517
|
-
log("prepareMemberElectionTrigger for election %d", electionStatus.electionIndex);
|
|
518
|
-
if (!electionStatus.canProceedToMemberPhase) {
|
|
519
|
-
log("Cannot proceed to member phase - not ready");
|
|
520
|
-
return null;
|
|
521
|
-
}
|
|
522
|
-
const params = await getElectionProposalParams(electionStatus.electionIndex, provider, nomineeGovernorAddress);
|
|
523
|
-
if (!params) {
|
|
524
|
-
log("Could not find proposal params for election %d", electionStatus.electionIndex);
|
|
525
|
-
return null;
|
|
526
|
-
}
|
|
527
|
-
return buildExecuteTransaction(params, nomineeGovernorAddress, `execute() on NomineeElectionGovernor to trigger member election #${electionStatus.electionIndex}`);
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Get proposal parameters for a member election
|
|
531
|
-
*
|
|
532
|
-
* Retrieves the targets, values, calldatas, and description hash needed to
|
|
533
|
-
* execute a member election proposal.
|
|
534
|
-
*
|
|
535
|
-
* @param electionIndex - Election index
|
|
536
|
-
* @param provider - L2 provider
|
|
537
|
-
* @param memberGovernorAddress - Optional governor address override
|
|
538
|
-
* @returns Proposal parameters or null if not found
|
|
539
|
-
*/
|
|
540
|
-
async function getMemberElectionProposalParams(electionIndex, provider, memberGovernorAddress = constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR) {
|
|
541
|
-
log("getMemberElectionProposalParams for index %d", electionIndex);
|
|
542
|
-
const memberGovernor = getMemberGovernor(memberGovernorAddress, provider);
|
|
543
|
-
// Compute proposal ID using getProposeArgs + hashProposal
|
|
544
|
-
const memberProposalId = await computeElectionProposalId(electionIndex, memberGovernor);
|
|
545
|
-
// Verify proposal exists by checking state
|
|
546
|
-
try {
|
|
547
|
-
await (0, rpc_utils_1.queryWithRetry)(() => memberGovernor.state(memberProposalId));
|
|
548
|
-
}
|
|
549
|
-
catch {
|
|
550
|
-
log("No member proposal found for election %d", electionIndex);
|
|
551
|
-
return null;
|
|
552
|
-
}
|
|
553
|
-
return findProposalCreatedParams(memberProposalId, memberGovernorAddress, memberGovernor, provider);
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Prepare a transaction to execute member election result
|
|
557
|
-
*
|
|
558
|
-
* After member voting succeeds, calling execute() on the MemberElectionGovernor
|
|
559
|
-
* installs the new Security Council members.
|
|
560
|
-
*
|
|
561
|
-
* @param electionStatus - Status from trackElectionProposal (must have canExecuteMember=true)
|
|
562
|
-
* @param provider - L2 provider
|
|
563
|
-
* @param memberGovernorAddress - Optional governor address override
|
|
564
|
-
* @returns Prepared transaction or null if not ready
|
|
565
|
-
*
|
|
566
|
-
* @example
|
|
567
|
-
* ```typescript
|
|
568
|
-
* const status = await trackElectionProposal(5, l2Provider, l1Provider);
|
|
569
|
-
*
|
|
570
|
-
* if (status.canExecuteMember) {
|
|
571
|
-
* const prepared = await prepareMemberElectionExecution(status, l2Provider);
|
|
572
|
-
* if (prepared) {
|
|
573
|
-
* const tx = await signer.sendTransaction({
|
|
574
|
-
* to: prepared.to,
|
|
575
|
-
* data: prepared.data,
|
|
576
|
-
* });
|
|
577
|
-
* await tx.wait();
|
|
578
|
-
* console.log("New Security Council members installed!");
|
|
579
|
-
* }
|
|
580
|
-
* }
|
|
581
|
-
* ```
|
|
582
|
-
*/
|
|
583
|
-
async function prepareMemberElectionExecution(electionStatus, provider, memberGovernorAddress = constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR) {
|
|
584
|
-
log("prepareMemberElectionExecution for election %d", electionStatus.electionIndex);
|
|
585
|
-
if (!electionStatus.canExecuteMember) {
|
|
586
|
-
log("Cannot execute member election - not ready");
|
|
587
|
-
return null;
|
|
588
|
-
}
|
|
589
|
-
const params = await getMemberElectionProposalParams(electionStatus.electionIndex, provider, memberGovernorAddress);
|
|
590
|
-
if (!params) {
|
|
591
|
-
log("Could not find proposal params for member election %d", electionStatus.electionIndex);
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
return buildExecuteTransaction(params, memberGovernorAddress, `execute() on MemberElectionGovernor to install new Security Council members for election #${electionStatus.electionIndex}`);
|
|
595
|
-
}
|
|
596
|
-
// Election Discovery & Batch Tracking
|
|
597
|
-
/**
|
|
598
|
-
* Find the election index for a given proposal ID
|
|
599
|
-
*
|
|
600
|
-
* Searches through elections to find which one contains the given proposal ID
|
|
601
|
-
* (either as nominee or member proposal).
|
|
602
|
-
*
|
|
603
|
-
* @param proposalId - The proposal ID to find
|
|
604
|
-
* @param l2Provider - L2 provider
|
|
605
|
-
* @param l1Provider - L1 provider (needed for trackElectionProposal)
|
|
606
|
-
* @returns Election index or null if not found
|
|
607
|
-
*/
|
|
608
|
-
async function getElectionIndexForProposalId(proposalId, l2Provider, l1Provider) {
|
|
609
|
-
log("getElectionIndexForProposalId: searching for proposal %s", proposalId);
|
|
610
|
-
// Get current election count
|
|
611
|
-
const status = await checkElectionStatus(l2Provider, l1Provider);
|
|
612
|
-
const electionCount = status.electionCount;
|
|
613
|
-
// Search through elections (most recent first for efficiency)
|
|
614
|
-
for (let i = electionCount - 1; i >= 0; i--) {
|
|
615
|
-
try {
|
|
616
|
-
const electionStatus = await trackElectionProposal(i, l2Provider, l1Provider);
|
|
617
|
-
if (electionStatus.nomineeProposalId === proposalId) {
|
|
618
|
-
log("Found proposal %s as nominee proposal for election %d", proposalId, i);
|
|
619
|
-
return i;
|
|
620
|
-
}
|
|
621
|
-
if (electionStatus.memberProposalId === proposalId) {
|
|
622
|
-
log("Found proposal %s as member proposal for election %d", proposalId, i);
|
|
623
|
-
return i;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
catch {
|
|
627
|
-
// Skip elections that fail to track
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
log("Proposal %s not found in any election", proposalId);
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Track all active elections (not yet completed)
|
|
636
|
-
*
|
|
637
|
-
* Returns election statuses for all elections that are still in progress.
|
|
638
|
-
*
|
|
639
|
-
* @param l2Provider - L2 provider
|
|
640
|
-
* @param l1Provider - L1 provider
|
|
641
|
-
* @returns Array of election statuses for active elections
|
|
642
|
-
*/
|
|
643
|
-
async function trackAllElections(l2Provider, l1Provider) {
|
|
644
|
-
log("trackAllElections: fetching all active elections");
|
|
645
|
-
const status = await checkElectionStatus(l2Provider, l1Provider);
|
|
646
|
-
const electionCount = status.electionCount;
|
|
647
|
-
const results = [];
|
|
648
|
-
// Track all elections (including completed for history)
|
|
649
|
-
for (let i = 0; i < electionCount; i++) {
|
|
650
|
-
try {
|
|
651
|
-
const electionStatus = await trackElectionProposal(i, l2Provider, l1Provider);
|
|
652
|
-
results.push(electionStatus);
|
|
653
|
-
}
|
|
654
|
-
catch (err) {
|
|
655
|
-
log("Failed to track election %d: %s", i, err);
|
|
656
|
-
// Skip failed elections
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
log("Tracked %d elections", results.length);
|
|
660
|
-
return results;
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Track only incomplete elections (not yet completed)
|
|
664
|
-
*
|
|
665
|
-
* Returns election statuses for elections that are still in progress.
|
|
666
|
-
* Completed elections are excluded.
|
|
667
|
-
*
|
|
668
|
-
* @param l2Provider - L2 provider
|
|
669
|
-
* @param l1Provider - L1 provider
|
|
670
|
-
* @returns Array of election statuses for incomplete elections
|
|
671
|
-
*/
|
|
672
|
-
async function trackIncompleteElections(l2Provider, l1Provider) {
|
|
673
|
-
const all = await trackAllElections(l2Provider, l1Provider);
|
|
674
|
-
return all.filter((e) => e.phase !== "COMPLETED");
|
|
675
|
-
}
|
|
676
|
-
// ============================================================================
|
|
677
|
-
// Detailed Election Tracking
|
|
678
|
-
// ============================================================================
|
|
679
|
-
/**
|
|
680
|
-
* Get all contenders who registered for a nominee election
|
|
681
|
-
*
|
|
682
|
-
* Fetches ContenderAdded events to build the list of registered contenders.
|
|
683
|
-
*
|
|
684
|
-
* @param proposalId - Nominee election proposal ID
|
|
685
|
-
* @param provider - L2 provider
|
|
686
|
-
* @param nomineeGovernorAddress - Optional governor address override
|
|
687
|
-
* @returns Array of contenders with registration info
|
|
688
|
-
*/
|
|
689
|
-
async function getContenders(proposalId, provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
690
|
-
log("getContenders for proposal %s", proposalId);
|
|
691
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress, provider);
|
|
692
|
-
const iface = new ethers_1.ethers.utils.Interface(abis_1.NOMINEE_ELECTION_GOVERNOR_ABI);
|
|
693
|
-
const { fromBlock, toBlock } = await getLogQueryBlockRange(governor, proposalId, provider);
|
|
694
|
-
const logs = await (0, rpc_utils_1.queryWithRetry)(() => provider.getLogs({
|
|
695
|
-
address: nomineeGovernorAddress,
|
|
696
|
-
topics: [
|
|
697
|
-
iface.getEventTopic("ContenderAdded"),
|
|
698
|
-
ethers_1.ethers.utils.hexZeroPad(ethers_1.BigNumber.from(proposalId).toHexString(), 32),
|
|
699
|
-
],
|
|
700
|
-
fromBlock,
|
|
701
|
-
toBlock,
|
|
702
|
-
}));
|
|
703
|
-
const contenders = logs.flatMap((eventLog) => {
|
|
704
|
-
try {
|
|
705
|
-
const parsed = iface.parseLog(eventLog);
|
|
706
|
-
return [
|
|
707
|
-
{
|
|
708
|
-
address: parsed.args.contender,
|
|
709
|
-
registeredAtBlock: eventLog.blockNumber,
|
|
710
|
-
registrationTxHash: eventLog.transactionHash,
|
|
711
|
-
},
|
|
712
|
-
];
|
|
713
|
-
}
|
|
714
|
-
catch {
|
|
715
|
-
return [];
|
|
716
|
-
}
|
|
717
|
-
});
|
|
718
|
-
log("Found %d contenders for proposal %s", contenders.length, proposalId);
|
|
719
|
-
return contenders;
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Get all nominees for a nominee election with their vote counts
|
|
723
|
-
*
|
|
724
|
-
* Fetches nominee list from contract and enriches with vote data.
|
|
725
|
-
*
|
|
726
|
-
* @param proposalId - Nominee election proposal ID
|
|
727
|
-
* @param provider - L2 provider
|
|
728
|
-
* @param nomineeGovernorAddress - Optional governor address override
|
|
729
|
-
* @returns Array of nominees with vote and exclusion data
|
|
730
|
-
*/
|
|
731
|
-
async function getNomineesWithVotes(proposalId, provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
732
|
-
log("getNomineesWithVotes for proposal %s", proposalId);
|
|
733
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress, provider);
|
|
734
|
-
const nomineeAddresses = await (0, rpc_utils_1.queryWithRetry)(() => governor.nominees(proposalId));
|
|
735
|
-
const nominees = await Promise.all(nomineeAddresses.map(async (addr) => {
|
|
736
|
-
const [votesReceived, isExcluded] = await Promise.all([
|
|
737
|
-
(0, rpc_utils_1.queryWithRetry)(() => governor.votesReceived(proposalId, addr)),
|
|
738
|
-
(0, rpc_utils_1.queryWithRetry)(() => governor.isExcluded(proposalId, addr)),
|
|
739
|
-
]);
|
|
740
|
-
return { address: addr, votesReceived, isExcluded };
|
|
741
|
-
}));
|
|
742
|
-
log("Found %d nominees for proposal %s", nominees.length, proposalId);
|
|
743
|
-
return nominees;
|
|
744
|
-
}
|
|
745
|
-
/**
|
|
746
|
-
* Get excluded nominees with exclusion details
|
|
747
|
-
*
|
|
748
|
-
* Fetches NomineeExcluded events to get exclusion information.
|
|
749
|
-
*
|
|
750
|
-
* @param proposalId - Nominee election proposal ID
|
|
751
|
-
* @param provider - L2 provider
|
|
752
|
-
* @param nomineeGovernorAddress - Optional governor address override
|
|
753
|
-
* @returns Array of excluded nominees with exclusion tx info
|
|
754
|
-
*/
|
|
755
|
-
async function getExcludedNominees(proposalId, provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
756
|
-
log("getExcludedNominees for proposal %s", proposalId);
|
|
757
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress, provider);
|
|
758
|
-
const iface = new ethers_1.ethers.utils.Interface(abis_1.NOMINEE_ELECTION_GOVERNOR_ABI);
|
|
759
|
-
const { fromBlock, toBlock } = await getLogQueryBlockRange(governor, proposalId, provider, 0);
|
|
760
|
-
const logs = await (0, rpc_utils_1.queryWithRetry)(() => provider.getLogs({
|
|
761
|
-
address: nomineeGovernorAddress,
|
|
762
|
-
topics: [
|
|
763
|
-
iface.getEventTopic("NomineeExcluded"),
|
|
764
|
-
ethers_1.ethers.utils.hexZeroPad(ethers_1.BigNumber.from(proposalId).toHexString(), 32),
|
|
765
|
-
],
|
|
766
|
-
fromBlock,
|
|
767
|
-
toBlock,
|
|
768
|
-
}));
|
|
769
|
-
const parsedLogs = logs.flatMap((eventLog) => {
|
|
770
|
-
try {
|
|
771
|
-
const parsed = iface.parseLog(eventLog);
|
|
772
|
-
return [{ eventLog, nominee: parsed.args.nominee }];
|
|
773
|
-
}
|
|
774
|
-
catch {
|
|
775
|
-
return [];
|
|
776
|
-
}
|
|
777
|
-
});
|
|
778
|
-
const excluded = await Promise.all(parsedLogs.map(async ({ eventLog, nominee }) => {
|
|
779
|
-
const votesReceived = await (0, rpc_utils_1.queryWithRetry)(() => governor.votesReceived(proposalId, nominee));
|
|
780
|
-
return {
|
|
781
|
-
address: nominee,
|
|
782
|
-
votesReceived,
|
|
783
|
-
isExcluded: true,
|
|
784
|
-
excludedAtBlock: eventLog.blockNumber,
|
|
785
|
-
exclusionTxHash: eventLog.transactionHash,
|
|
786
|
-
};
|
|
787
|
-
}));
|
|
788
|
-
log("Found %d excluded nominees for proposal %s", excluded.length, proposalId);
|
|
789
|
-
return excluded;
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Get detailed nominee election information
|
|
793
|
-
*
|
|
794
|
-
* Aggregates contenders, nominees, excluded nominees, and voting data.
|
|
795
|
-
*
|
|
796
|
-
* @param electionIndex - Election index
|
|
797
|
-
* @param provider - L2 provider
|
|
798
|
-
* @param nomineeGovernorAddress - Optional governor address override
|
|
799
|
-
* @returns Detailed nominee election data or null if not found
|
|
800
|
-
*/
|
|
801
|
-
async function getNomineeElectionDetails(electionIndex, provider, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
802
|
-
log("getNomineeElectionDetails for election %d", electionIndex);
|
|
803
|
-
const proposalId = await getElectionProposalId(electionIndex, provider, nomineeGovernorAddress);
|
|
804
|
-
if (!proposalId) {
|
|
805
|
-
log("No proposal found for election %d", electionIndex);
|
|
806
|
-
return null;
|
|
807
|
-
}
|
|
808
|
-
const governor = getNomineeGovernor(nomineeGovernorAddress, provider);
|
|
809
|
-
const [contenders, nominees, snapshotBlock] = await Promise.all([
|
|
810
|
-
getContenders(proposalId, provider, nomineeGovernorAddress),
|
|
811
|
-
getNomineesWithVotes(proposalId, provider, nomineeGovernorAddress),
|
|
812
|
-
(0, rpc_utils_1.queryWithRetry)(() => governor.proposalSnapshot(proposalId)),
|
|
813
|
-
]);
|
|
814
|
-
const quorumThreshold = await (0, rpc_utils_1.queryWithRetry)(() => governor.quorum(snapshotBlock.toNumber()));
|
|
815
|
-
const compliantNominees = nominees.filter((n) => !n.isExcluded);
|
|
816
|
-
const excludedNominees = nominees.filter((n) => n.isExcluded);
|
|
817
|
-
return {
|
|
818
|
-
proposalId,
|
|
819
|
-
electionIndex,
|
|
820
|
-
contenders,
|
|
821
|
-
nominees,
|
|
822
|
-
compliantNominees,
|
|
823
|
-
excludedNominees,
|
|
824
|
-
quorumThreshold,
|
|
825
|
-
targetNomineeCount: constants_1.TIMING.SECURITY_COUNCIL_TARGET_NOMINEES,
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
/**
|
|
829
|
-
* Get member election results with weighted votes
|
|
830
|
-
*
|
|
831
|
-
* Fetches top nominees (winners) and their weighted vote totals.
|
|
832
|
-
*
|
|
833
|
-
* @param electionIndex - Election index
|
|
834
|
-
* @param provider - L2 provider
|
|
835
|
-
* @param memberGovernorAddress - Optional governor address override
|
|
836
|
-
* @param nomineeGovernorAddress - Optional nominee governor address override
|
|
837
|
-
* @returns Detailed member election data or null if not found
|
|
838
|
-
*/
|
|
839
|
-
async function getMemberElectionDetails(electionIndex, provider, memberGovernorAddress = constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR, nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR) {
|
|
840
|
-
log("getMemberElectionDetails for election %d", electionIndex);
|
|
841
|
-
const memberGovernor = getMemberGovernor(memberGovernorAddress, provider);
|
|
842
|
-
const nomineeGovernor = getNomineeGovernor(nomineeGovernorAddress, provider);
|
|
843
|
-
// Compute proposal ID using getProposeArgs + hashProposal (same scheme as nominee governor)
|
|
844
|
-
const memberProposalId = await computeElectionProposalId(electionIndex, memberGovernor);
|
|
845
|
-
// Verify proposal exists by checking state (reverts for non-existent proposals)
|
|
846
|
-
try {
|
|
847
|
-
await (0, rpc_utils_1.queryWithRetry)(() => memberGovernor.state(memberProposalId));
|
|
848
|
-
}
|
|
849
|
-
catch {
|
|
850
|
-
log("No member proposal found for election %d", electionIndex);
|
|
851
|
-
return null;
|
|
852
|
-
}
|
|
853
|
-
const [winners, deadline, fullWeightDeadline, nomineeProposalId] = await Promise.all([
|
|
854
|
-
(0, rpc_utils_1.queryWithRetry)(() => memberGovernor.topNominees(memberProposalId)).catch(() => []),
|
|
855
|
-
(0, rpc_utils_1.queryWithRetry)(() => memberGovernor.proposalDeadline(memberProposalId)),
|
|
856
|
-
(0, rpc_utils_1.queryWithRetry)(() => memberGovernor.fullWeightVotingDeadline(memberProposalId)),
|
|
857
|
-
getElectionProposalId(electionIndex, provider, nomineeGovernorAddress),
|
|
858
|
-
]);
|
|
859
|
-
const allNominees = nomineeProposalId
|
|
860
|
-
? await (0, rpc_utils_1.queryWithRetry)(() => nomineeGovernor.compliantNominees(nomineeProposalId))
|
|
861
|
-
: [];
|
|
862
|
-
const winnersSet = new Set(winners.map((w) => w.toLowerCase()));
|
|
863
|
-
const nomineeWeights = await Promise.all(allNominees.map(async (addr) => ({
|
|
864
|
-
addr,
|
|
865
|
-
weight: await (0, rpc_utils_1.queryWithRetry)(() => memberGovernor.weightReceived(memberProposalId, addr)),
|
|
866
|
-
})));
|
|
867
|
-
const nomineeDetails = nomineeWeights
|
|
868
|
-
.sort((a, b) => (b.weight.gt(a.weight) ? 1 : -1))
|
|
869
|
-
.map((n, i) => ({
|
|
870
|
-
address: n.addr,
|
|
871
|
-
weightReceived: n.weight,
|
|
872
|
-
isWinner: winnersSet.has(n.addr.toLowerCase()),
|
|
873
|
-
rank: i + 1,
|
|
874
|
-
}));
|
|
875
|
-
return {
|
|
876
|
-
proposalId: memberProposalId,
|
|
877
|
-
electionIndex,
|
|
878
|
-
nominees: nomineeDetails,
|
|
879
|
-
winners,
|
|
880
|
-
fullWeightDeadline: fullWeightDeadline.toNumber(),
|
|
881
|
-
proposalDeadline: deadline.toNumber(),
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
//# sourceMappingURL=election.js.map
|