@gzeoneth/gov-tracker 0.3.0 → 0.4.0-beta.18db0de

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.
Files changed (125) hide show
  1. package/README.md +0 -1
  2. package/dist/calldata/decoder.d.ts.map +1 -1
  3. package/dist/calldata/decoder.js +10 -56
  4. package/dist/calldata/decoder.js.map +1 -1
  5. package/dist/calldata/parameter-decoder.d.ts +21 -1
  6. package/dist/calldata/parameter-decoder.d.ts.map +1 -1
  7. package/dist/calldata/parameter-decoder.js +30 -0
  8. package/dist/calldata/parameter-decoder.js.map +1 -1
  9. package/dist/cli/lib/cli.d.ts.map +1 -1
  10. package/dist/cli/lib/cli.js +30 -42
  11. package/dist/cli/lib/cli.js.map +1 -1
  12. package/dist/cli/tui/utils/index.d.ts +1 -1
  13. package/dist/cli/tui/utils/index.d.ts.map +1 -1
  14. package/dist/cli/tui/utils/index.js +1 -2
  15. package/dist/cli/tui/utils/index.js.map +1 -1
  16. package/dist/cli/tui/utils/stage-formatter.d.ts +1 -1
  17. package/dist/cli/tui/utils/stage-formatter.d.ts.map +1 -1
  18. package/dist/cli/tui/utils/stage-formatter.js +1 -2
  19. package/dist/cli/tui/utils/stage-formatter.js.map +1 -1
  20. package/dist/cli/tui/views/ElectionView.d.ts.map +1 -1
  21. package/dist/cli/tui/views/ElectionView.js +3 -3
  22. package/dist/cli/tui/views/ElectionView.js.map +1 -1
  23. package/dist/cli/tui/views/StageView.d.ts.map +1 -1
  24. package/dist/cli/tui/views/StageView.js +1 -1
  25. package/dist/cli/tui/views/StageView.js.map +1 -1
  26. package/dist/data/bundled-cache.json +21513 -22619
  27. package/dist/discovery/governor-discovery.d.ts.map +1 -1
  28. package/dist/discovery/governor-discovery.js +43 -48
  29. package/dist/discovery/governor-discovery.js.map +1 -1
  30. package/dist/discovery/security-council.d.ts +15 -0
  31. package/dist/discovery/security-council.d.ts.map +1 -1
  32. package/dist/discovery/security-council.js +42 -19
  33. package/dist/discovery/security-council.js.map +1 -1
  34. package/dist/discovery/timelock-discovery.d.ts.map +1 -1
  35. package/dist/discovery/timelock-discovery.js +34 -54
  36. package/dist/discovery/timelock-discovery.js.map +1 -1
  37. package/dist/election/details.d.ts.map +1 -1
  38. package/dist/election/details.js +28 -30
  39. package/dist/election/details.js.map +1 -1
  40. package/dist/election/index.d.ts +3 -6
  41. package/dist/election/index.d.ts.map +1 -1
  42. package/dist/election/index.js +7 -14
  43. package/dist/election/index.js.map +1 -1
  44. package/dist/election/params.d.ts +8 -1
  45. package/dist/election/params.d.ts.map +1 -1
  46. package/dist/election/params.js +45 -1
  47. package/dist/election/params.js.map +1 -1
  48. package/dist/election/participants.d.ts.map +1 -1
  49. package/dist/election/participants.js +21 -24
  50. package/dist/election/participants.js.map +1 -1
  51. package/dist/election/proposal-ids.d.ts +10 -0
  52. package/dist/election/proposal-ids.d.ts.map +1 -1
  53. package/dist/election/proposal-ids.js +45 -5
  54. package/dist/election/proposal-ids.js.map +1 -1
  55. package/dist/index.d.ts +4 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +15 -8
  58. package/dist/index.js.map +1 -1
  59. package/dist/simulation/simulation-data.js +2 -2
  60. package/dist/simulation/simulation-data.js.map +1 -1
  61. package/dist/stages/builder.js +1 -1
  62. package/dist/stages/builder.js.map +1 -1
  63. package/dist/stages/l2-to-l1-message.d.ts.map +1 -1
  64. package/dist/stages/l2-to-l1-message.js +2 -8
  65. package/dist/stages/l2-to-l1-message.js.map +1 -1
  66. package/dist/stages/proposal-queued.d.ts.map +1 -1
  67. package/dist/stages/proposal-queued.js +4 -1
  68. package/dist/stages/proposal-queued.js.map +1 -1
  69. package/dist/stages/retryables.d.ts +4 -3
  70. package/dist/stages/retryables.d.ts.map +1 -1
  71. package/dist/stages/retryables.js +25 -48
  72. package/dist/stages/retryables.js.map +1 -1
  73. package/dist/stages/timelock.d.ts +1 -0
  74. package/dist/stages/timelock.d.ts.map +1 -1
  75. package/dist/stages/timelock.js +44 -58
  76. package/dist/stages/timelock.js.map +1 -1
  77. package/dist/stages/utils.d.ts +39 -5
  78. package/dist/stages/utils.d.ts.map +1 -1
  79. package/dist/stages/utils.js +102 -40
  80. package/dist/stages/utils.js.map +1 -1
  81. package/dist/tracker/discovery.d.ts.map +1 -1
  82. package/dist/tracker/discovery.js +4 -9
  83. package/dist/tracker/discovery.js.map +1 -1
  84. package/dist/tracker/pipeline.d.ts +25 -6
  85. package/dist/tracker/pipeline.d.ts.map +1 -1
  86. package/dist/tracker/pipeline.js +428 -211
  87. package/dist/tracker/pipeline.js.map +1 -1
  88. package/dist/tracker/query.d.ts +20 -0
  89. package/dist/tracker/query.d.ts.map +1 -1
  90. package/dist/tracker/query.js +86 -1
  91. package/dist/tracker/query.js.map +1 -1
  92. package/dist/tracker/stage-runner.d.ts +62 -0
  93. package/dist/tracker/stage-runner.d.ts.map +1 -0
  94. package/dist/tracker/stage-runner.js +80 -0
  95. package/dist/tracker/stage-runner.js.map +1 -0
  96. package/dist/tracker/state.d.ts +64 -0
  97. package/dist/tracker/state.d.ts.map +1 -1
  98. package/dist/tracker/state.js +191 -12
  99. package/dist/tracker/state.js.map +1 -1
  100. package/dist/tracker.d.ts +30 -1
  101. package/dist/tracker.d.ts.map +1 -1
  102. package/dist/tracker.js +220 -167
  103. package/dist/tracker.js.map +1 -1
  104. package/dist/types/core.d.ts +3 -2
  105. package/dist/types/core.d.ts.map +1 -1
  106. package/dist/types/core.js +3 -2
  107. package/dist/types/core.js.map +1 -1
  108. package/dist/types/tracking.d.ts +11 -0
  109. package/dist/types/tracking.d.ts.map +1 -1
  110. package/dist/utils/log-filters.d.ts.map +1 -1
  111. package/dist/utils/log-filters.js +4 -8
  112. package/dist/utils/log-filters.js.map +1 -1
  113. package/dist/utils/timing.d.ts +10 -3
  114. package/dist/utils/timing.d.ts.map +1 -1
  115. package/dist/utils/timing.js +39 -13
  116. package/dist/utils/timing.js.map +1 -1
  117. package/package.json +7 -6
  118. package/dist/election/prepare.d.ts +0 -10
  119. package/dist/election/prepare.d.ts.map +0 -1
  120. package/dist/election/prepare.js +0 -52
  121. package/dist/election/prepare.js.map +0 -1
  122. package/dist/election/tracking.d.ts +0 -28
  123. package/dist/election/tracking.d.ts.map +0 -1
  124. package/dist/election/tracking.js +0 -412
  125. package/dist/election/tracking.js.map +0 -1
@@ -1,15 +1,26 @@
1
1
  "use strict";
2
2
  /**
3
- * Pipeline Stage Functions
3
+ * Pipeline Stage Tracking
4
4
  *
5
- * Pure functions that track stages and return updated state.
6
- * Each function reads from state, performs tracking, and returns new state.
5
+ * Declarative pipelines for tracking governance proposals and elections.
6
+ * Each stage is a config object with type and track function.
7
+ * The stage runner handles caching automatically.
8
+ *
9
+ * Three tracking paths:
10
+ * - Governor: PROPOSAL_CREATED → VOTING_ACTIVE → PROPOSAL_QUEUED → timelock
11
+ * - Timelock: L2_TIMELOCK → L2_TO_L1_MESSAGE → L1_TIMELOCK → RETRYABLE_EXECUTED
12
+ * - Election: CREATE_ELECTION → NOMINEE_ELECTION → NOMINEE_VETTING → MEMBER_ELECTION → timelock
7
13
  */
8
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getElectionContext = getElectionContext;
16
+ exports.proposalStateToStageStatus = proposalStateToStageStatus;
9
17
  exports.trackGovernorPipeline = trackGovernorPipeline;
10
18
  exports.trackTimelockPipeline = trackTimelockPipeline;
19
+ exports.trackElectionPipeline = trackElectionPipeline;
20
+ const ethers_1 = require("ethers");
11
21
  const logger_1 = require("../utils/logger");
12
22
  const state_1 = require("./state");
23
+ const stage_runner_1 = require("./stage-runner");
13
24
  const utils_1 = require("../stages/utils");
14
25
  const proposal_created_1 = require("../stages/proposal-created");
15
26
  const voting_1 = require("../stages/voting");
@@ -20,171 +31,148 @@ const l2_to_l1_message_1 = require("../stages/l2-to-l1-message");
20
31
  const retryables_1 = require("../stages/retryables");
21
32
  const constants_1 = require("../constants");
22
33
  const timing_2 = require("../utils/timing");
23
- // Logging
34
+ const builder_1 = require("../stages/builder");
35
+ const rpc_utils_1 = require("../utils/rpc-utils");
36
+ const multicall_1 = require("../utils/multicall");
37
+ const abis_1 = require("../abis");
38
+ const contracts_1 = require("../election/contracts");
39
+ const proposal_ids_1 = require("../election/proposal-ids");
40
+ const timelock_discovery_1 = require("../discovery/timelock-discovery");
41
+ const governor_discovery_1 = require("../discovery/governor-discovery");
42
+ const log_search_1 = require("../utils/log-search");
24
43
  const { pipeline: log, tracker: logTracker } = logger_1.loggers;
25
- // Stage chain mapping for L1 stages
26
- // L1_TIMELOCK is the only L1 stage; RETRYABLE_EXECUTED runs on L2 (Arb1/Nova)
27
- const L1_STAGES = new Set(["L1_TIMELOCK"]);
28
- // Helper: create placeholder stage
29
- const placeholder = (type, status, reason) =>
30
- // Assertion needed: placeholder stages have partial data that gets filled later
31
- ({
32
- type,
33
- status,
34
- chain: L1_STAGES.has(type) ? "ethereum" : "arb1",
35
- chainId: L1_STAGES.has(type) ? 1 : 42161,
36
- transactions: [],
37
- data: { reason },
38
- });
39
- // Helper: track with cache check
40
- async function withCache(state, stageType, key, onCached, onTrack) {
41
- const cached = (0, state_1.getCompletedStage)(state, stageType);
42
- if (cached) {
43
- log("%s: using cached stage", stageType);
44
- return { state: await (0, state_1.addStage)(state, cached), [key]: onCached(cached) };
45
- }
46
- return onTrack();
44
+ /** Guard for election stages - returns election index and nominee proposal ID if available */
45
+ function getElectionContext(state) {
46
+ const electionIndex = (0, state_1.getElectionIndex)(state);
47
+ const nomineeProposalId = (0, state_1.getNomineeProposalId)(state);
48
+ return electionIndex !== undefined && nomineeProposalId
49
+ ? { electionIndex, nomineeProposalId }
50
+ : null;
47
51
  }
48
- // Helper: track stage with error handling
49
- async function track(state, stageType, key, tracker) {
50
- try {
51
- const { stage, result } = await tracker();
52
- return { state: await (0, state_1.addStage)(state, stage), [key]: result };
52
+ /** Map proposal state string to stage status */
53
+ function proposalStateToStageStatus(proposalState) {
54
+ if (proposalState === "Active" || proposalState === "Pending") {
55
+ return { status: "PENDING", complete: false };
53
56
  }
54
- catch (error) {
55
- throw new Error(`Failed to track ${stageType}: ${error instanceof Error ? error.message : String(error)}`);
57
+ if (proposalState === "Defeated" || proposalState === "Canceled") {
58
+ return { status: "FAILED", complete: false };
56
59
  }
60
+ return { status: "COMPLETED", complete: true };
57
61
  }
58
- // Governor Stages (1-3)
59
- async function pipelineTrackProposalCreated(state) {
62
+ // ============================================================================
63
+ // Governor Stage Trackers
64
+ // ============================================================================
65
+ async function trackProposalCreatedStage(state) {
60
66
  const governorAddress = (0, state_1.getGovernorAddress)(state);
61
67
  const proposalId = (0, state_1.getProposalId)(state);
62
68
  if (!governorAddress || !proposalId)
63
- return { state, found: false };
69
+ return { state, continue: false };
64
70
  if ((0, state_1.getIsElection)(state))
65
- return { state, found: false };
66
- return withCache(state, "PROPOSAL_CREATED", "found", () => true, async () => {
67
- log("PROPOSAL_CREATED: tracking");
68
- const creationTxHash = state.input.type === "governor" ? state.input.creationTxHash : undefined;
69
- return track(state, "PROPOSAL_CREATED", "found", async () => {
70
- const r = await (0, proposal_created_1.trackProposalCreated)(governorAddress, proposalId, state.providers.l2, {
71
- creationTxHash,
72
- chunkSize: state.chunkingConfig.l2ChunkSize,
73
- });
74
- return { stage: r.stage, result: r.proposalData !== null };
75
- });
71
+ return { state, continue: false };
72
+ log("PROPOSAL_CREATED: tracking");
73
+ const creationTxHash = state.input.type === "governor" ? state.input.creationTxHash : undefined;
74
+ const result = await (0, proposal_created_1.trackProposalCreated)(governorAddress, proposalId, state.providers.l2, {
75
+ creationTxHash,
76
+ chunkSize: state.chunkingConfig.l2ChunkSize,
76
77
  });
78
+ return {
79
+ state: await (0, state_1.addStage)(state, result.stage),
80
+ continue: result.proposalData !== null,
81
+ };
77
82
  }
78
- async function pipelineTrackVoting(state) {
83
+ async function trackVotingStage_(state) {
79
84
  const governorAddress = (0, state_1.getGovernorAddress)(state);
80
85
  const proposalId = (0, state_1.getProposalId)(state);
81
86
  const proposalData = (0, state_1.getProposalData)(state);
82
87
  if (!governorAddress || !proposalId || !proposalData)
83
- return { state, complete: false };
84
- return withCache(state, "VOTING_ACTIVE", "complete", (c) => c.status === "COMPLETED", async () => {
85
- log("VOTING_ACTIVE: tracking");
86
- return track(state, "VOTING_ACTIVE", "complete", async () => {
87
- const r = await (0, voting_1.trackVotingStage)(governorAddress, proposalId, proposalData, state.providers.l2);
88
- return { stage: r.stage, result: r.stage.status === "COMPLETED" };
89
- });
90
- });
88
+ return { state, continue: false };
89
+ log("VOTING_ACTIVE: tracking");
90
+ const result = await (0, voting_1.trackVotingStage)(governorAddress, proposalId, proposalData, state.providers.l2);
91
+ return {
92
+ state: await (0, state_1.addStage)(state, result.stage),
93
+ continue: result.stage.status === "COMPLETED",
94
+ };
91
95
  }
92
- async function pipelineTrackProposalQueued(state) {
96
+ async function trackProposalQueuedStage(state) {
93
97
  const governorAddress = (0, state_1.getGovernorAddress)(state);
94
98
  const proposalId = (0, state_1.getProposalId)(state);
95
99
  const proposalData = (0, state_1.getProposalData)(state);
96
100
  if (!governorAddress || !proposalId)
97
- return { state, queued: false };
98
- return withCache(state, "PROPOSAL_QUEUED", "queued", () => true, async () => {
99
- log("PROPOSAL_QUEUED: tracking");
100
- return track(state, "PROPOSAL_QUEUED", "queued", async () => {
101
- // Voting deadline is L1 block number - convert to L2 for searching
102
- // Use bounds to speed up binary search: min=creation block, max=creation+7M (~20 days)
103
- const votingDeadlineL1 = (0, state_1.getVotingEndBlock)(state);
104
- let votingEndBlock;
105
- if (votingDeadlineL1) {
106
- const creationBlock = proposalData?.creationBlock ?? 0;
107
- const { blockNumber: currentL2Block } = await (0, timing_2.getCurrentBlockInfo)(state.providers.l2);
108
- const maxL2Block = Math.min(creationBlock + 7000000, currentL2Block);
109
- votingEndBlock = await (0, timing_1.getFirstL2BlockForL1Block)(state.providers.l2, votingDeadlineL1, {
110
- minL2Block: creationBlock,
111
- maxL2Block,
112
- });
113
- }
114
- const r = await (0, proposal_queued_1.trackProposalQueued)(governorAddress, proposalId, state.providers.l2, proposalData?.creationBlock ?? 0, { votingEndBlock, chunkSize: state.chunkingConfig.l2ChunkSize });
115
- let stage = r.stage;
116
- if (stage.type === "PROPOSAL_QUEUED" && stage.status === "READY" && proposalData) {
117
- // Enrich stage data with proposal info for READY state
118
- stage = {
119
- ...stage,
120
- data: {
121
- ...stage.data,
122
- targets: proposalData.targets,
123
- values: Array.from(proposalData.values).map((v) => v.toString()),
124
- calldatas: proposalData.calldatas,
125
- description: proposalData.description,
126
- },
127
- };
128
- }
129
- return { stage, result: r.operationId !== null && r.timelockAddress !== null };
101
+ return { state, continue: false };
102
+ log("PROPOSAL_QUEUED: tracking");
103
+ // Convert L1 voting deadline to L2 block for searching
104
+ const votingDeadlineL1 = (0, state_1.getVotingEndBlock)(state);
105
+ let votingEndBlock;
106
+ if (votingDeadlineL1) {
107
+ const creationBlock = proposalData?.creationBlock ?? 0;
108
+ const { blockNumber: currentL2Block } = await (0, timing_2.getCurrentBlockInfo)(state.providers.l2);
109
+ const maxL2Block = Math.min(creationBlock + 7000000, currentL2Block);
110
+ votingEndBlock = await (0, timing_1.getFirstL2BlockForL1Block)(state.providers.l2, votingDeadlineL1, {
111
+ minL2Block: creationBlock,
112
+ maxL2Block,
130
113
  });
131
- });
114
+ }
115
+ const result = await (0, proposal_queued_1.trackProposalQueued)(governorAddress, proposalId, state.providers.l2, proposalData?.creationBlock ?? 0, { votingEndBlock, chunkSize: state.chunkingConfig.l2ChunkSize });
116
+ let stage = result.stage;
117
+ // Enrich stage data with proposal info for READY state
118
+ if (stage.type === "PROPOSAL_QUEUED" && stage.status === "READY" && proposalData) {
119
+ stage = {
120
+ ...stage,
121
+ data: {
122
+ ...stage.data,
123
+ targets: proposalData.targets,
124
+ values: Array.from(proposalData.values).map((v) => v.toString()),
125
+ calldatas: proposalData.calldatas,
126
+ description: proposalData.description,
127
+ },
128
+ };
129
+ }
130
+ return {
131
+ state: await (0, state_1.addStage)(state, stage),
132
+ continue: result.operationId !== null && result.timelockAddress !== null,
133
+ };
132
134
  }
133
- // Timelock Stage (4)
134
- async function pipelineTrackL2Timelock(state) {
135
+ // ============================================================================
136
+ // Timelock Stage Trackers
137
+ // ============================================================================
138
+ async function trackL2TimelockStage(state) {
135
139
  const timelockAddress = (0, state_1.getTimelockAddress)(state);
136
140
  const operationId = (0, state_1.getOperationId)(state);
137
- // timelockAddress and operationId are needed for both cached and fresh tracking
141
+ const firstCallScheduledData = (0, state_1.getFirstCallScheduledData)(state);
138
142
  if (!timelockAddress || !operationId)
139
- return { state, executed: false };
140
- // Check for completed cached stage first (zero-RPC resume path)
141
- const cached = (0, state_1.getCompletedStage)(state, "L2_TIMELOCK");
142
- if (cached) {
143
- log("L2_TIMELOCK: using cached stage");
144
- return { state: await (0, state_1.addStage)(state, cached), executed: cached.status === "COMPLETED" };
145
- }
143
+ return { state, continue: false };
146
144
  // Fresh tracking requires callScheduledData
147
- const firstCallScheduledData = (0, state_1.getFirstCallScheduledData)(state);
148
145
  if (!firstCallScheduledData) {
149
146
  log("L2_TIMELOCK: missing callScheduledData for fresh tracking");
150
- return { state, executed: false };
147
+ return { state, continue: false };
151
148
  }
152
149
  log("L2_TIMELOCK: tracking");
153
- return track(state, "L2_TIMELOCK", "executed", async () => {
154
- const r = await (0, timelock_1.trackL2Timelock)(timelockAddress, operationId, state.providers.l2, (0, state_1.getQueueBlockNumber)(state) ?? 0, firstCallScheduledData, {
155
- cachedExecutionTxHash: (0, state_1.getL2ExecutionTxHash)(state),
156
- allStages: state.stages,
157
- chunkSize: state.chunkingConfig.l2ChunkSize,
158
- });
159
- return { stage: r.stage, result: r.executionTxHash !== null };
150
+ const result = await (0, timelock_1.trackL2Timelock)(timelockAddress, operationId, state.providers.l2, (0, state_1.getQueueBlockNumber)(state) ?? 0, firstCallScheduledData, {
151
+ cachedExecutionTxHash: (0, state_1.getL2ExecutionTxHash)(state),
152
+ allStages: state.stages,
153
+ proposalType: (0, state_1.getProposalType)(state),
154
+ chunkSize: state.chunkingConfig.l2ChunkSize,
160
155
  });
156
+ return {
157
+ state: await (0, state_1.addStage)(state, result.stage),
158
+ continue: result.executionTxHash !== null,
159
+ };
161
160
  }
162
- // L2→L1 Message Stage (5) - Unified
163
161
  const L1_ROUNDTRIP_STAGES = ["L2_TO_L1_MESSAGE", "L1_TIMELOCK", "RETRYABLE_EXECUTED"];
164
- async function addSkippedL1Stages(state) {
165
- let s = state;
166
- for (const type of L1_ROUNDTRIP_STAGES)
167
- s = await (0, state_1.addStage)(s, placeholder(type, "SKIPPED", "L2-only path"));
168
- return s;
169
- }
170
- async function pipelineTrackL2ToL1Message(state) {
162
+ async function trackL2ToL1MessageStage(state) {
171
163
  const l2ExecutionTxHash = (0, state_1.getL2ExecutionTxHash)(state);
172
164
  const addressForPath = (0, state_1.getGovernorAddress)(state) ?? (0, state_1.getTimelockAddress)(state);
173
165
  const needsL1 = addressForPath ? (0, utils_1.isConstitutional)(addressForPath) : true;
166
+ // L2-only path: skip all L1 stages
174
167
  if (!needsL1) {
175
168
  log("L2_TO_L1_MESSAGE: L2-only path, skipping L1 stages");
176
- return { state: await addSkippedL1Stages(state), executed: false, needsL1: false };
169
+ const newState = await (0, stage_runner_1.addPlaceholders)(state, L1_ROUNDTRIP_STAGES, "SKIPPED", "L2-only path");
170
+ return { state: newState, continue: false };
177
171
  }
178
172
  if (!l2ExecutionTxHash)
179
- return { state, executed: false, needsL1: true };
180
- // Check completed cache
181
- const cached = (0, state_1.getCompletedStage)(state, "L2_TO_L1_MESSAGE");
182
- if (cached) {
183
- log("L2_TO_L1_MESSAGE: using cached stage");
184
- return { state: await (0, state_1.addStage)(state, cached), executed: true, needsL1: true };
185
- }
173
+ return { state, continue: false };
186
174
  // Fast-path for pending (still in challenge period)
187
- const pending = (0, state_1.getCachedStage)(state, "L2_TO_L1_MESSAGE");
175
+ const pending = (0, stage_runner_1.getCachedStage)(state, "L2_TO_L1_MESSAGE");
188
176
  if (pending?.type === "L2_TO_L1_MESSAGE" && pending.status === "PENDING") {
189
177
  const firstExec = pending.data.firstExecutableBlock;
190
178
  if (firstExec) {
@@ -200,112 +188,341 @@ async function pipelineTrackL2ToL1Message(state) {
200
188
  delaySeconds: remainingSeconds,
201
189
  },
202
190
  };
203
- return { state: await (0, state_1.addStage)(state, updated), executed: false, needsL1: true };
191
+ return { state: await (0, state_1.addStage)(state, updated), continue: false };
204
192
  }
205
193
  }
206
194
  }
207
195
  // Full tracking
208
196
  log("L2_TO_L1_MESSAGE: tracking");
209
- const { state: newState, executed } = await track(state, "L2_TO_L1_MESSAGE", "executed", async () => {
210
- const r = await (0, l2_to_l1_message_1.trackL2ToL1Message)(l2ExecutionTxHash, state.providers.l2, state.providers.l1, {
211
- chunkSize: state.chunkingConfig.l1ChunkSize,
212
- });
213
- return { stage: r.stage, result: r.isExecuted };
214
- });
215
- return { state: newState, executed, needsL1: true };
197
+ const result = await (0, l2_to_l1_message_1.trackL2ToL1Message)(l2ExecutionTxHash, state.providers.l2, state.providers.l1, { chunkSize: state.chunkingConfig.l1ChunkSize });
198
+ return {
199
+ state: await (0, state_1.addStage)(state, result.stage),
200
+ continue: result.isExecuted,
201
+ };
216
202
  }
217
- // L1 Timelock Stage (6)
218
- async function pipelineTrackL1Timelock(state) {
219
- return withCache(state, "L1_TIMELOCK", "executed", (c) => c.status === "COMPLETED", async () => {
220
- log("L1_TIMELOCK: tracking");
221
- return track(state, "L1_TIMELOCK", "executed", async () => {
222
- const r = await (0, timelock_1.trackL1Timelock)(state.providers.l1, {
223
- outboxExecutionTx: (0, state_1.getOutboxExecutionTx)(state),
224
- fromBlock: (0, state_1.getFirstExecutableBlock)(state),
225
- allStages: state.stages,
226
- chunkSize: state.chunkingConfig.l1ChunkSize,
227
- });
228
- return { stage: r.stage, result: r.executionTxHash !== null };
229
- });
203
+ async function trackL1TimelockStage(state) {
204
+ log("L1_TIMELOCK: tracking");
205
+ const result = await (0, timelock_1.trackL1Timelock)(state.providers.l1, {
206
+ outboxExecutionTx: (0, state_1.getOutboxExecutionTx)(state),
207
+ fromBlock: (0, state_1.getFirstExecutableBlock)(state),
208
+ allStages: state.stages,
209
+ chunkSize: state.chunkingConfig.l1ChunkSize,
230
210
  });
211
+ return {
212
+ state: await (0, state_1.addStage)(state, result.stage),
213
+ continue: result.executionTxHash !== null,
214
+ };
231
215
  }
232
- // Retryable Stage (7)
233
- async function pipelineTrackRetryables(state) {
216
+ async function trackRetryablesStage(state) {
234
217
  const l1ExecutionTxHash = (0, state_1.getL1ExecutionTxHash)(state);
235
218
  if (!l1ExecutionTxHash)
236
- return { state, redeemed: false };
237
- return withCache(state, "RETRYABLE_EXECUTED", "redeemed", (c) => c.status === "COMPLETED", async () => {
238
- log("RETRYABLE_EXECUTED: tracking");
239
- return track(state, "RETRYABLE_EXECUTED", "redeemed", async () => {
240
- const r = await (0, retryables_1.trackRetryables)(l1ExecutionTxHash, state.providers.l1, {
241
- l2Provider: state.providers.l2,
242
- novaProvider: state.providers.nova,
243
- });
244
- return { stage: r.stage, result: r.stage.status === "COMPLETED" };
245
- });
219
+ return { state, continue: false };
220
+ log("RETRYABLE_EXECUTED: tracking");
221
+ const result = await (0, retryables_1.trackRetryables)(l1ExecutionTxHash, state.providers.l1, {
222
+ l2Provider: state.providers.l2,
223
+ novaProvider: state.providers.nova,
246
224
  });
225
+ return {
226
+ state: await (0, state_1.addStage)(state, result.stage),
227
+ continue: result.stage.status === "COMPLETED",
228
+ };
229
+ }
230
+ // ============================================================================
231
+ // Election Stage Trackers
232
+ // ============================================================================
233
+ async function findElectionExecuteTxHash(proposalId, governorAddress, provider, l2ChunkSize = 100000) {
234
+ const governor = (0, contracts_1.getNomineeGovernor)(governorAddress, provider);
235
+ const topic = abis_1.proposalExecutedInterface.getEventTopic("ProposalExecuted");
236
+ const snapshotL1 = await (0, rpc_utils_1.queryWithRetry)(() => governor.proposalSnapshot(proposalId));
237
+ const snapshotL2 = await (0, timing_1.getFirstL2BlockForL1Block)(provider, snapshotL1.toNumber());
238
+ if (!snapshotL2)
239
+ return null;
240
+ const currentBlock = await (0, rpc_utils_1.queryWithRetry)(() => provider.getBlockNumber());
241
+ const matchingLog = await (0, log_search_1.findLog)(provider, { address: governorAddress, topics: [topic], fromBlock: snapshotL2, toBlock: currentBlock }, (eventLog) => {
242
+ try {
243
+ const parsed = abis_1.proposalExecutedInterface.parseLog(eventLog);
244
+ return parsed.args.proposalId.toString() === proposalId;
245
+ }
246
+ catch {
247
+ return false;
248
+ }
249
+ }, { chunkSize: l2ChunkSize });
250
+ return matchingLog
251
+ ? { txHash: matchingLog.transactionHash, blockNumber: matchingLog.blockNumber }
252
+ : null;
247
253
  }
248
- // Full Pipelines
249
- async function addPlaceholders(state, types, reason) {
250
- let s = state;
251
- for (const type of types)
252
- s = await (0, state_1.addStage)(s, placeholder(type, "NOT_STARTED", reason));
253
- return s;
254
+ async function trackCreateElectionStage(state) {
255
+ const electionIndex = (0, state_1.getElectionIndex)(state);
256
+ if (electionIndex === undefined)
257
+ return { state, continue: false };
258
+ log("CREATE_ELECTION: tracking election %d", electionIndex);
259
+ const nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR;
260
+ const nomineeGovernor = (0, contracts_1.getNomineeGovernor)(nomineeGovernorAddress, state.providers.l2);
261
+ const cohort = (await (0, rpc_utils_1.queryWithRetry)(() => nomineeGovernor.electionIndexToCohort(electionIndex)));
262
+ const nomineeProposalId = await (0, proposal_ids_1.getElectionProposalId)(electionIndex, state.providers.l2, nomineeGovernorAddress);
263
+ if (!nomineeProposalId) {
264
+ const notStartedStage = new builder_1.StageBuilder("CREATE_ELECTION", "arb1")
265
+ .status("NOT_STARTED")
266
+ .data({ electionIndex, cohort, startTimestamp: 0, reason: "Election not yet created" })
267
+ .build();
268
+ return { state: await (0, state_1.addStage)(state, notStartedStage), continue: false };
269
+ }
270
+ const stageBuilder = new builder_1.StageBuilder("CREATE_ELECTION", "arb1")
271
+ .status("COMPLETED")
272
+ .data({ electionIndex, cohort, startTimestamp: 0, nomineeProposalId });
273
+ // Find creation tx hash for the election proposal
274
+ try {
275
+ const creationEvent = await (0, governor_discovery_1.findProposalCreatedEvent)(nomineeGovernorAddress, nomineeProposalId, state.providers.l2);
276
+ if (creationEvent) {
277
+ stageBuilder.tx(creationEvent.creationTxHash, creationEvent.creationBlock, "arb1", 42161);
278
+ }
279
+ }
280
+ catch {
281
+ // Creation tx discovery failed silently - stage still valid
282
+ }
283
+ return { state: await (0, state_1.addStage)(state, stageBuilder.build()), continue: true };
254
284
  }
255
- const RETRYABLE_STAGES = ["RETRYABLE_EXECUTED"];
285
+ async function trackNomineeElectionStage(state) {
286
+ const ctx = getElectionContext(state);
287
+ if (!ctx)
288
+ return { state, continue: false };
289
+ log("NOMINEE_ELECTION: tracking");
290
+ const nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR;
291
+ const nomineeResults = await (0, multicall_1.multicall)(state.providers.l2, [
292
+ (0, multicall_1.buildCallInput)(nomineeGovernorAddress, abis_1.nomineeElectionGovernorInterface, "state", [
293
+ ctx.nomineeProposalId,
294
+ ]),
295
+ (0, multicall_1.buildCallInput)(nomineeGovernorAddress, abis_1.nomineeElectionGovernorInterface, "compliantNomineeCount", [ctx.nomineeProposalId]),
296
+ ]);
297
+ const nomineeState = nomineeResults[0];
298
+ const nomineeProposalState = (0, constants_1.proposalStateToString)(nomineeState);
299
+ const compliantNomineeCount = (nomineeResults[1] ?? ethers_1.BigNumber.from(0)).toNumber();
300
+ const { status, complete } = proposalStateToStageStatus(nomineeProposalState);
301
+ const stageBuilder = new builder_1.StageBuilder("NOMINEE_ELECTION", "arb1").status(status).data({
302
+ nomineeProposalId: ctx.nomineeProposalId,
303
+ proposalState: nomineeProposalState,
304
+ contenderCount: 0,
305
+ compliantNomineeCount,
306
+ targetNomineeCount: constants_1.TIMING.SECURITY_COUNCIL_TARGET_NOMINEES,
307
+ });
308
+ return { state: await (0, state_1.addStage)(state, stageBuilder.build()), continue: complete };
309
+ }
310
+ async function trackNomineeVettingStage(state) {
311
+ const ctx = getElectionContext(state);
312
+ if (!ctx)
313
+ return { state, continue: false };
314
+ log("NOMINEE_VETTING: tracking");
315
+ const nomineeGovernorAddress = constants_1.ADDRESSES.ELECTION_NOMINEE_GOVERNOR;
316
+ const memberGovernorAddress = constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR;
317
+ const memberGovernor = (0, contracts_1.getMemberGovernor)(memberGovernorAddress, state.providers.l2);
318
+ const [vettingResults, currentL1Block] = await Promise.all([
319
+ (0, multicall_1.multicall)(state.providers.l2, [
320
+ (0, multicall_1.buildCallInput)(nomineeGovernorAddress, abis_1.nomineeElectionGovernorInterface, "proposalVettingDeadline", [ctx.nomineeProposalId]),
321
+ (0, multicall_1.buildCallInput)(nomineeGovernorAddress, abis_1.nomineeElectionGovernorInterface, "compliantNomineeCount", [ctx.nomineeProposalId]),
322
+ (0, multicall_1.buildCallInput)(nomineeGovernorAddress, abis_1.nomineeElectionGovernorInterface, "state", [
323
+ ctx.nomineeProposalId,
324
+ ]),
325
+ ]),
326
+ (0, timing_1.getL1BlockNumberFromL2)(state.providers.l2),
327
+ ]);
328
+ const vettingDeadlineBN = vettingResults[0] ?? ethers_1.BigNumber.from(0);
329
+ const vettingDeadline = vettingDeadlineBN.toNumber();
330
+ const compliantNomineeCount = (vettingResults[1] ?? ethers_1.BigNumber.from(0)).toNumber();
331
+ const nomineeState = vettingResults[2];
332
+ const nomineeProposalState = (0, constants_1.proposalStateToString)(nomineeState);
333
+ const isInVettingPeriod = nomineeProposalState === "Succeeded" && currentL1Block.lte(vettingDeadlineBN);
334
+ // Check if member proposal exists
335
+ let memberProposalId = null;
336
+ const computedMemberProposalId = await (0, proposal_ids_1.computeElectionProposalId)(ctx.electionIndex, memberGovernor);
337
+ try {
338
+ await (0, rpc_utils_1.queryWithRetry)(() => memberGovernor.state(computedMemberProposalId));
339
+ memberProposalId = computedMemberProposalId;
340
+ }
341
+ catch {
342
+ // Member election not yet created
343
+ }
344
+ const stageBuilder = new builder_1.StageBuilder("NOMINEE_VETTING", "arb1").data({
345
+ nomineeProposalId: ctx.nomineeProposalId,
346
+ vettingDeadline,
347
+ currentL1Block: currentL1Block.toNumber(),
348
+ compliantNomineeCount,
349
+ memberProposalId: memberProposalId ?? undefined,
350
+ });
351
+ const canProceedToMemberPhase = nomineeProposalState === "Succeeded" &&
352
+ !isInVettingPeriod &&
353
+ compliantNomineeCount >= constants_1.TIMING.SECURITY_COUNCIL_TARGET_NOMINEES &&
354
+ !memberProposalId;
355
+ let complete = false;
356
+ if (nomineeProposalState === "Executed" || memberProposalId) {
357
+ stageBuilder.status("COMPLETED");
358
+ complete = true;
359
+ try {
360
+ const executeEvent = await findElectionExecuteTxHash(ctx.nomineeProposalId, nomineeGovernorAddress, state.providers.l2, state.chunkingConfig.l2ChunkSize);
361
+ if (executeEvent) {
362
+ stageBuilder.tx(executeEvent.txHash, executeEvent.blockNumber, "arb1", 42161);
363
+ }
364
+ }
365
+ catch {
366
+ // TX hash discovery failed
367
+ }
368
+ }
369
+ else if (canProceedToMemberPhase) {
370
+ stageBuilder.status("READY").executable(true);
371
+ }
372
+ else if (isInVettingPeriod) {
373
+ stageBuilder.status("PENDING");
374
+ }
375
+ else if (compliantNomineeCount < constants_1.TIMING.SECURITY_COUNCIL_TARGET_NOMINEES) {
376
+ stageBuilder.status("FAILED");
377
+ }
378
+ return {
379
+ state: await (0, state_1.addStage)(state, stageBuilder.build()),
380
+ continue: complete && memberProposalId !== null,
381
+ };
382
+ }
383
+ async function trackMemberElectionStage(state) {
384
+ const electionIndex = (0, state_1.getElectionIndex)(state);
385
+ const memberProposalId = (0, state_1.getMemberProposalId)(state);
386
+ if (electionIndex === undefined || !memberProposalId)
387
+ return { state, continue: false };
388
+ log("MEMBER_ELECTION: tracking");
389
+ const memberGovernorAddress = constants_1.ADDRESSES.ELECTION_MEMBER_GOVERNOR;
390
+ const memberGovernor = (0, contracts_1.getMemberGovernor)(memberGovernorAddress, state.providers.l2);
391
+ const memberState = await (0, rpc_utils_1.queryWithRetry)(() => memberGovernor.state(memberProposalId));
392
+ const memberProposalState = (0, constants_1.proposalStateToString)(memberState);
393
+ const stageBuilder = new builder_1.StageBuilder("MEMBER_ELECTION", "arb1").data({
394
+ memberProposalId,
395
+ proposalState: memberProposalState,
396
+ winnersCount: 0,
397
+ });
398
+ let executed = false;
399
+ let newState = state;
400
+ if (memberProposalState === "Active" || memberProposalState === "Pending") {
401
+ stageBuilder.status("PENDING");
402
+ }
403
+ else if (memberProposalState === "Succeeded") {
404
+ stageBuilder.status("READY").executable(true);
405
+ }
406
+ else if (memberProposalState === "Executed") {
407
+ stageBuilder.status("COMPLETED");
408
+ executed = true;
409
+ try {
410
+ const executeEvent = await findElectionExecuteTxHash(memberProposalId, memberGovernorAddress, state.providers.l2, state.chunkingConfig.l2ChunkSize);
411
+ if (executeEvent) {
412
+ stageBuilder.tx(executeEvent.txHash, executeEvent.blockNumber, "arb1", 42161, {
413
+ description: "executed",
414
+ });
415
+ // Find CallScheduled events and inject into state for timelock pipeline
416
+ const callScheduledEvents = await (0, timelock_discovery_1.findCallScheduledByTxHash)(executeEvent.txHash, state.providers.l2);
417
+ if (callScheduledEvents?.length) {
418
+ const firstEvent = callScheduledEvents[0];
419
+ stageBuilder.data({
420
+ memberProposalId,
421
+ proposalState: memberProposalState,
422
+ winnersCount: 0,
423
+ operationId: firstEvent.operationId,
424
+ timelockAddress: firstEvent.timelockAddress,
425
+ });
426
+ // Inject callScheduledData for timelock pipeline
427
+ newState = { ...state, callScheduledData: callScheduledEvents };
428
+ }
429
+ }
430
+ }
431
+ catch {
432
+ // TX hash discovery failed
433
+ }
434
+ }
435
+ else if (memberProposalState === "Defeated" || memberProposalState === "Canceled") {
436
+ stageBuilder.status("FAILED");
437
+ }
438
+ return {
439
+ state: await (0, state_1.addStage)(newState, stageBuilder.build()),
440
+ continue: executed,
441
+ };
442
+ }
443
+ // ============================================================================
444
+ // Stage Configurations
445
+ // ============================================================================
446
+ const GOVERNOR_STAGES = [
447
+ { type: "PROPOSAL_CREATED", track: trackProposalCreatedStage },
448
+ { type: "VOTING_ACTIVE", track: trackVotingStage_ },
449
+ { type: "PROPOSAL_QUEUED", track: trackProposalQueuedStage },
450
+ ];
451
+ const TIMELOCK_STAGES = [
452
+ {
453
+ type: "L2_TIMELOCK",
454
+ track: trackL2TimelockStage,
455
+ // Custom cache: also check if we have callScheduledData
456
+ checkCache: async (state) => {
457
+ const timelockAddress = (0, state_1.getTimelockAddress)(state);
458
+ const operationId = (0, state_1.getOperationId)(state);
459
+ if (!timelockAddress || !operationId)
460
+ return { state, continue: false };
461
+ const cached = (0, stage_runner_1.getCachedStage)(state, "L2_TIMELOCK");
462
+ if (cached && (0, utils_1.isStageSuccess)(cached.status)) {
463
+ log("L2_TIMELOCK: cached");
464
+ return { state: await (0, state_1.addStage)(state, cached), continue: true };
465
+ }
466
+ return undefined; // Fall through to track
467
+ },
468
+ },
469
+ {
470
+ type: "L2_TO_L1_MESSAGE",
471
+ track: trackL2ToL1MessageStage,
472
+ // Custom cache: handle pending fast-path in track function
473
+ checkCache: async (state) => {
474
+ const cached = (0, stage_runner_1.getCachedStage)(state, "L2_TO_L1_MESSAGE");
475
+ if (cached && (0, utils_1.isStageSuccess)(cached.status)) {
476
+ log("L2_TO_L1_MESSAGE: cached");
477
+ return { state: await (0, state_1.addStage)(state, cached), continue: true };
478
+ }
479
+ return undefined;
480
+ },
481
+ },
482
+ { type: "L1_TIMELOCK", track: trackL1TimelockStage },
483
+ { type: "RETRYABLE_EXECUTED", track: trackRetryablesStage },
484
+ ];
485
+ const ELECTION_STAGES = [
486
+ { type: "CREATE_ELECTION", track: trackCreateElectionStage },
487
+ { type: "NOMINEE_ELECTION", track: trackNomineeElectionStage },
488
+ { type: "NOMINEE_VETTING", track: trackNomineeVettingStage },
489
+ { type: "MEMBER_ELECTION", track: trackMemberElectionStage },
490
+ ];
491
+ // ============================================================================
492
+ // Pipeline Exports
493
+ // ============================================================================
256
494
  /**
257
- * Track full governor proposal pipeline.
258
- * Returns final state after tracking all stages.
495
+ * Track governor proposal pipeline (stages 1-7).
259
496
  */
260
497
  async function trackGovernorPipeline(state) {
261
- // Stage 1: Proposal Created
262
- const { state: state1, found } = await pipelineTrackProposalCreated(state);
263
- if (!found) {
264
- logTracker("proposal not found, stopping");
265
- return state1;
266
- }
267
- // Stage 2: Voting
268
- const { state: state2, complete: votingComplete } = await pipelineTrackVoting(state1);
269
- if (!votingComplete) {
270
- logTracker("voting not complete, stopping");
271
- return state2;
498
+ logTracker("running governor pipeline");
499
+ state = await (0, stage_runner_1.runPipeline)(state, GOVERNOR_STAGES);
500
+ // Continue with timelock if proposal was queued
501
+ const queued = state.stages.find((s) => s.type === "PROPOSAL_QUEUED");
502
+ if (queued?.status === "COMPLETED") {
503
+ return (0, stage_runner_1.runPipeline)(state, TIMELOCK_STAGES);
272
504
  }
273
- // Stage 3: Proposal Queued
274
- const { state: state3, queued } = await pipelineTrackProposalQueued(state2);
275
- if (!queued) {
276
- logTracker("proposal not queued, stopping");
277
- return state3;
278
- }
279
- // Continue with timelock pipeline
280
- return trackTimelockPipeline(state3);
505
+ return state;
281
506
  }
282
507
  /**
283
508
  * Track timelock pipeline (stages 4-7).
284
- * Used by governor pipeline and direct timelock tracking.
509
+ * Used by governor pipeline, election pipeline, and direct timelock tracking.
285
510
  */
286
511
  async function trackTimelockPipeline(state) {
287
- // Stage 4: L2 Timelock (unified)
288
- const { state: state1, executed: l2Executed } = await pipelineTrackL2Timelock(state);
289
- if (!l2Executed) {
290
- logTracker("L2 timelock not executed, stopping");
291
- return addPlaceholders(state1, L1_ROUNDTRIP_STAGES, "L2 timelock not executed");
292
- }
293
- // Stage 5: L2→L1 Message (unified)
294
- const { state: state2, executed: msgExecuted, needsL1, } = await pipelineTrackL2ToL1Message(state1);
295
- if (!needsL1)
296
- return state2; // L2-only path, already added SKIPPED stages
297
- if (!msgExecuted) {
298
- logTracker("L2→L1 message not executed, stopping");
299
- return addPlaceholders(state2, L1_ROUNDTRIP_STAGES.slice(1), "Waiting for L2→L1 message");
300
- }
301
- // Stage 6: L1 Timelock (unified)
302
- const { state: state3, executed: l1Executed } = await pipelineTrackL1Timelock(state2);
303
- if (!l1Executed) {
304
- logTracker("L1 timelock not executed, stopping");
305
- return addPlaceholders(state3, RETRYABLE_STAGES, "L1 timelock not executed");
512
+ logTracker("running timelock pipeline");
513
+ return (0, stage_runner_1.runPipeline)(state, TIMELOCK_STAGES);
514
+ }
515
+ /**
516
+ * Track election pipeline (stages 1-8).
517
+ */
518
+ async function trackElectionPipeline(state) {
519
+ logTracker("running election pipeline");
520
+ state = await (0, stage_runner_1.runPipeline)(state, ELECTION_STAGES);
521
+ // Continue with timelock if member election was executed
522
+ const memberStage = state.stages.find((s) => s.type === "MEMBER_ELECTION");
523
+ if (memberStage?.status === "COMPLETED") {
524
+ return (0, stage_runner_1.runPipeline)(state, TIMELOCK_STAGES);
306
525
  }
307
- // Stage 7: Retryables
308
- const { state: finalState } = await pipelineTrackRetryables(state3);
309
- return finalState;
526
+ return state;
310
527
  }
311
528
  //# sourceMappingURL=pipeline.js.map