@aztec/ethereum 0.0.1-commit.993d240 → 0.0.1-commit.9a89641

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 (105) hide show
  1. package/dest/client.d.ts +11 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +35 -3
  4. package/dest/config.d.ts +22 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +54 -3
  7. package/dest/contracts/governance.d.ts +36 -11
  8. package/dest/contracts/governance.d.ts.map +1 -1
  9. package/dest/contracts/governance.js +54 -24
  10. package/dest/contracts/governance_proposer.d.ts +7 -8
  11. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  12. package/dest/contracts/governance_proposer.js +7 -10
  13. package/dest/contracts/gse.d.ts +8 -2
  14. package/dest/contracts/gse.d.ts.map +1 -1
  15. package/dest/contracts/gse.js +11 -2
  16. package/dest/contracts/multicall.d.ts +5 -3
  17. package/dest/contracts/multicall.d.ts.map +1 -1
  18. package/dest/contracts/multicall.js +7 -5
  19. package/dest/contracts/outbox.d.ts +8 -2
  20. package/dest/contracts/outbox.d.ts.map +1 -1
  21. package/dest/contracts/outbox.js +6 -2
  22. package/dest/contracts/rollup.d.ts +176 -9
  23. package/dest/contracts/rollup.d.ts.map +1 -1
  24. package/dest/contracts/rollup.js +48 -8
  25. package/dest/contracts/slashing_proposer.d.ts +37 -6
  26. package/dest/contracts/slashing_proposer.d.ts.map +1 -1
  27. package/dest/contracts/slashing_proposer.js +447 -17
  28. package/dest/deploy_aztec_l1_contracts.d.ts +16 -5
  29. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  30. package/dest/deploy_aztec_l1_contracts.js +26 -13
  31. package/dest/foundry_binary.d.ts +13 -0
  32. package/dest/foundry_binary.d.ts.map +1 -0
  33. package/dest/foundry_binary.js +52 -0
  34. package/dest/l1_artifacts.d.ts +1242 -95
  35. package/dest/l1_artifacts.d.ts.map +1 -1
  36. package/dest/l1_tx_utils/config.js +3 -3
  37. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +23 -4
  38. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -1
  39. package/dest/l1_tx_utils/l1_fee_analyzer.js +65 -3
  40. package/dest/l1_tx_utils/l1_tx_utils.d.ts +1 -1
  41. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  42. package/dest/l1_tx_utils/l1_tx_utils.js +84 -22
  43. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  44. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  45. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +2 -1
  46. package/dest/l1_tx_utils/types.d.ts +27 -1
  47. package/dest/l1_tx_utils/types.d.ts.map +1 -1
  48. package/dest/l1_tx_utils/types.js +10 -0
  49. package/dest/publisher_manager.d.ts +18 -6
  50. package/dest/publisher_manager.d.ts.map +1 -1
  51. package/dest/publisher_manager.js +44 -7
  52. package/dest/queries.d.ts +1 -1
  53. package/dest/queries.d.ts.map +1 -1
  54. package/dest/queries.js +7 -1
  55. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  56. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  57. package/dest/test/blob_kzg_warmup.js +64 -0
  58. package/dest/test/chain_monitor.d.ts +33 -2
  59. package/dest/test/chain_monitor.d.ts.map +1 -1
  60. package/dest/test/chain_monitor.js +71 -6
  61. package/dest/test/eth_cheat_codes.d.ts +26 -3
  62. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  63. package/dest/test/eth_cheat_codes.js +72 -37
  64. package/dest/test/index.d.ts +2 -1
  65. package/dest/test/index.d.ts.map +1 -1
  66. package/dest/test/index.js +1 -0
  67. package/dest/test/rollup_cheat_codes.d.ts +30 -1
  68. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  69. package/dest/test/rollup_cheat_codes.js +27 -0
  70. package/dest/test/start_anvil.d.ts +1 -1
  71. package/dest/test/start_anvil.d.ts.map +1 -1
  72. package/dest/test/start_anvil.js +41 -5
  73. package/dest/utils.d.ts +2 -2
  74. package/dest/utils.d.ts.map +1 -1
  75. package/dest/utils.js +15 -6
  76. package/package.json +5 -6
  77. package/src/client.ts +39 -1
  78. package/src/config.ts +73 -3
  79. package/src/contracts/governance.ts +63 -20
  80. package/src/contracts/governance_proposer.ts +11 -15
  81. package/src/contracts/gse.ts +19 -2
  82. package/src/contracts/multicall.ts +8 -5
  83. package/src/contracts/outbox.ts +6 -2
  84. package/src/contracts/rollup.ts +57 -8
  85. package/src/contracts/slashing_proposer.ts +78 -15
  86. package/src/deploy_aztec_l1_contracts.ts +31 -13
  87. package/src/foundry_binary.ts +57 -0
  88. package/src/l1_tx_utils/config.ts +3 -3
  89. package/src/l1_tx_utils/l1_fee_analyzer.ts +75 -3
  90. package/src/l1_tx_utils/l1_tx_utils.ts +71 -20
  91. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +2 -1
  92. package/src/l1_tx_utils/types.ts +32 -0
  93. package/src/publisher_manager.ts +54 -18
  94. package/src/queries.ts +6 -0
  95. package/src/test/blob_kzg_warmup.ts +65 -0
  96. package/src/test/chain_monitor.ts +88 -5
  97. package/src/test/eth_cheat_codes.ts +66 -41
  98. package/src/test/index.ts +1 -0
  99. package/src/test/rollup_cheat_codes.ts +51 -0
  100. package/src/test/start_anvil.ts +38 -6
  101. package/src/utils.ts +12 -6
  102. package/dest/generated/l1-contracts-defaults.d.ts +0 -30
  103. package/dest/generated/l1-contracts-defaults.d.ts.map +0 -1
  104. package/dest/generated/l1-contracts-defaults.js +0 -30
  105. package/src/generated/l1-contracts-defaults.ts +0 -32
@@ -1,16 +1,41 @@
1
1
  import { createLogger } from '@aztec/foundation/log';
2
2
  import { makeBackoff, retry } from '@aztec/foundation/retry';
3
- import { fileURLToPath } from '@aztec/foundation/url';
4
3
  import { spawn } from 'child_process';
5
- import { dirname, resolve } from 'path';
4
+ import { resolveFoundryBinary } from '../foundry_binary.js';
5
+ // Watchdog wrapper: instead of spawning anvil directly, we spawn a small bash supervisor that runs
6
+ // anvil as a background child and polls its own parent (this node process). If the parent dies for
7
+ // ANY reason — including SIGKILL / crash / OOM, where node's own exit handlers never run — the poll
8
+ // loop ends and the EXIT trap reaps anvil. The script is inlined (rather than shipped as a `.sh`) so
9
+ // it works from the published npm tarball too, and the resolved anvil binary is passed via
10
+ // `$ANVIL_BIN` so it works without `anvil` on PATH.
11
+ //
12
+ // `$@` is the anvil argv; `bash -c <script> bash <...args>` puts the args in `$@` and `$0` = 'bash'.
13
+ //
14
+ // The EXIT trap reaps anvil; INT/TERM just `exit` (which fires the EXIT trap) so a signal terminates
15
+ // the supervisor promptly instead of being swallowed — a trapped TERM does NOT terminate the shell,
16
+ // so trapping the kill directly on TERM would leave the poll loop running and the caller's teardown
17
+ // hanging until its SIGKILL escalation. `sleep & wait` makes the poll interruptible, so INT/TERM are
18
+ // handled immediately rather than after the current `sleep` returns.
19
+ const ANVIL_WATCHDOG = `
20
+ set -u
21
+ parent=$PPID
22
+ "$ANVIL_BIN" "$@" &
23
+ anvil_pid=$!
24
+ trap 'kill "$anvil_pid" 2>/dev/null' EXIT
25
+ trap 'exit 0' INT TERM
26
+ while kill -0 "$parent" 2>/dev/null; do sleep 1 & wait $!; done
27
+ `;
6
28
  /**
7
29
  * Ensures there's a running Anvil instance and returns the RPC URL.
8
30
  */ export async function startAnvil(opts = {}) {
9
- const anvilBinary = resolve(dirname(fileURLToPath(import.meta.url)), '../../', 'scripts/anvil_kill_wrapper.sh');
31
+ const anvilBinary = resolveFoundryBinary('anvil');
10
32
  const logger = opts.log ? createLogger('ethereum:anvil') : undefined;
11
33
  const methodCalls = opts.captureMethodCalls ? [] : undefined;
12
34
  let detectedPort;
13
35
  const anvil = await retry(async ()=>{
36
+ // `--port 0` lets anvil bind an OS-assigned ephemeral port; the actual port is read back from
37
+ // its "Listening on host:port" stdout below, so independent suites can spawn their own anvil
38
+ // in parallel without fighting over a fixed port.
14
39
  const port = opts.port ?? (process.env.ANVIL_PORT ? parseInt(process.env.ANVIL_PORT) : 8545);
15
40
  const args = [
16
41
  '--host',
@@ -31,7 +56,14 @@ import { dirname, resolve } from 'path';
31
56
  args.push('--hardfork', opts.hardfork);
32
57
  }
33
58
  args.push('--slots-in-an-epoch', String(opts.slotsInAnEpoch ?? 1));
34
- const child = spawn(anvilBinary, args, {
59
+ // Spawn the watchdog (see ANVIL_WATCHDOG). It launches anvil with these args and reaps it if we
60
+ // die; `$0` is 'bash' and `$@` is the anvil argv.
61
+ const child = spawn('bash', [
62
+ '-c',
63
+ ANVIL_WATCHDOG,
64
+ 'bash',
65
+ ...args
66
+ ], {
35
67
  stdio: [
36
68
  'ignore',
37
69
  'pipe',
@@ -39,6 +71,7 @@ import { dirname, resolve } from 'path';
39
71
  ],
40
72
  env: {
41
73
  ...process.env,
74
+ ANVIL_BIN: anvilBinary,
42
75
  RAYON_NUM_THREADS: '1'
43
76
  }
44
77
  });
@@ -139,7 +172,10 @@ import { dirname, resolve } from 'path';
139
172
  }
140
173
  }
141
174
  }
142
- /** Send SIGTERM, wait up to 5 s, then SIGKILL. All timers are always cleared. */ function killChild(child) {
175
+ /**
176
+ * Send SIGTERM to the watchdog, wait up to 5 s, then SIGKILL. The watchdog's trap forwards the
177
+ * signal to anvil, so terminating it tears down anvil too. All timers are always cleared.
178
+ */ function killChild(child) {
143
179
  return new Promise((resolve)=>{
144
180
  if (child.exitCode !== null || child.killed) {
145
181
  child.stdout?.destroy();
package/dest/utils.d.ts CHANGED
@@ -9,7 +9,7 @@ export interface L2Claim {
9
9
  }
10
10
  export declare class FormattedViemError extends Error {
11
11
  metaMessages?: any[];
12
- constructor(message: string, metaMessages?: any[]);
12
+ constructor(message: string, metaMessages?: any[], options?: ErrorOptions);
13
13
  }
14
14
  export declare function extractEvent<const TAbi extends Abi | readonly unknown[], TEventName extends ContractEventName<TAbi>, TEventType = DecodeEventLogReturnType<TAbi, TEventName, Hex[], undefined, true>>(logs: Log[], address: Hex, abi: TAbi, eventName: TEventName, filter?: (log: TEventType) => boolean, logger?: Logger): TEventType;
15
15
  export declare function tryExtractEvent<const TAbi extends Abi | readonly unknown[], TEventName extends ContractEventName<TAbi>, TEventType = DecodeEventLogReturnType<TAbi, TEventName, Hex[], undefined, true>>(logs: Log[], address: Hex, abi: TAbi, eventName: TEventName, filter?: (log: TEventType) => boolean, logger?: Logger): TEventType | undefined;
@@ -42,4 +42,4 @@ export declare function isBlobTransaction(tx: FormattedTransaction): tx is Forma
42
42
  * Calculates a percentile from an array of bigints
43
43
  */
44
44
  export declare function calculatePercentile(values: bigint[], percentile: number): bigint;
45
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN6RCxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUdwRCxPQUFPLEVBQ0wsS0FBSyxHQUFHLEVBR1IsS0FBSyxpQkFBaUIsRUFFdEIsS0FBSyx3QkFBd0IsRUFDN0IsS0FBSyxvQkFBb0IsRUFDekIsS0FBSyxHQUFHLEVBQ1IsS0FBSyxHQUFHLEVBR1QsTUFBTSxNQUFNLENBQUM7QUFHZCxNQUFNLFdBQVcsT0FBTztJQUN0QixXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ2hCLFdBQVcsRUFBRSxFQUFFLENBQUM7SUFDaEIsV0FBVyxFQUFFLEdBQUcsQ0FBQztJQUNqQixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7Q0FDMUI7QUFFRCxxQkFBYSxrQkFBbUIsU0FBUSxLQUFLO0lBQzNDLFlBQVksQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO0lBRXJCLFlBQVksT0FBTyxFQUFFLE1BQU0sRUFBRSxZQUFZLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFJaEQ7Q0FDRjtBQUVELHdCQUFnQixZQUFZLENBQzFCLEtBQUssQ0FBQyxJQUFJLFNBQVMsR0FBRyxHQUFHLFNBQVMsT0FBTyxFQUFFLEVBQzNDLFVBQVUsU0FBUyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsRUFDMUMsVUFBVSxHQUFHLHdCQUF3QixDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsR0FBRyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxFQUUvRSxJQUFJLEVBQUUsR0FBRyxFQUFFLEVBQ1gsT0FBTyxFQUFFLEdBQUcsRUFDWixHQUFHLEVBQUUsSUFBSSxFQUNULFNBQVMsRUFBRSxVQUFVLEVBQ3JCLE1BQU0sQ0FBQyxFQUFFLENBQUMsR0FBRyxFQUFFLFVBQVUsS0FBSyxPQUFPLEVBQ3JDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sR0FDZCxVQUFVLENBTVo7QUFFRCx3QkFBZ0IsZUFBZSxDQUM3QixLQUFLLENBQUMsSUFBSSxTQUFTLEdBQUcsR0FBRyxTQUFTLE9BQU8sRUFBRSxFQUMzQyxVQUFVLFNBQVMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLEVBQzFDLFVBQVUsR0FBRyx3QkFBd0IsQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsRUFFL0UsSUFBSSxFQUFFLEdBQUcsRUFBRSxFQUNYLE9BQU8sRUFBRSxHQUFHLEVBQ1osR0FBRyxFQUFFLElBQUksRUFDVCxTQUFTLEVBQUUsVUFBVSxFQUNyQixNQUFNLENBQUMsRUFBRSxDQUFDLEdBQUcsRUFBRSxVQUFVLEtBQUssT0FBTyxFQUNyQyxNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsVUFBVSxHQUFHLFNBQVMsQ0FnQnhCO0FBRUQsd0JBQWdCLHFCQUFxQixDQUFDLEdBQUcsRUFBRSxHQUFHLE9BVzdDO0FBRUQsd0JBQWdCLFNBQVMsQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLEdBQUcsR0FBRyxDQWUxQztBQTJFRDs7Ozs7R0FLRztBQUNILHdCQUFnQixlQUFlLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxHQUFHLEdBQUUsR0FBZSxHQUFHLGtCQUFrQixDQTREcEY7QUF5QkQ7Ozs7O0dBS0c7QUFDSCx3QkFBZ0IscUJBQXFCLENBQUMsSUFBSSxFQUFFLEdBQUcsR0FBRyxTQUFTLEVBQUUsR0FBRyxFQUFFLEdBQUcsR0FBRyxNQUFNLEdBQUcsU0FBUyxDQVV6RjtBQUVELHdCQUFnQixxQkFBcUIsQ0FBQyxHQUFHLEVBQUUsR0FBRyxzQkFhN0M7QUFFRDs7O0dBR0c7QUFDSCx3QkFBZ0IsaUJBQWlCLENBQUMsRUFBRSxFQUFFLG9CQUFvQixHQUFHLEVBQUUsSUFBSSxvQkFBb0IsR0FBRztJQUN4RixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7SUFDekIsbUJBQW1CLEVBQUUsU0FBUyxHQUFHLEVBQUUsQ0FBQztDQUNyQyxDQU9BO0FBRUQ7O0dBRUc7QUFDSCx3QkFBZ0IsbUJBQW1CLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxFQUFFLFVBQVUsRUFBRSxNQUFNLEdBQUcsTUFBTSxDQU9oRiJ9
45
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN6RCxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUdwRCxPQUFPLEVBQ0wsS0FBSyxHQUFHLEVBR1IsS0FBSyxpQkFBaUIsRUFFdEIsS0FBSyx3QkFBd0IsRUFDN0IsS0FBSyxvQkFBb0IsRUFDekIsS0FBSyxHQUFHLEVBQ1IsS0FBSyxHQUFHLEVBR1QsTUFBTSxNQUFNLENBQUM7QUFHZCxNQUFNLFdBQVcsT0FBTztJQUN0QixXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ2hCLFdBQVcsRUFBRSxFQUFFLENBQUM7SUFDaEIsV0FBVyxFQUFFLEdBQUcsQ0FBQztJQUNqQixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7Q0FDMUI7QUFFRCxxQkFBYSxrQkFBbUIsU0FBUSxLQUFLO0lBQzNDLFlBQVksQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO0lBRXJCLFlBQVksT0FBTyxFQUFFLE1BQU0sRUFBRSxZQUFZLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFBRSxPQUFPLENBQUMsRUFBRSxZQUFZLEVBSXhFO0NBQ0Y7QUFFRCx3QkFBZ0IsWUFBWSxDQUMxQixLQUFLLENBQUMsSUFBSSxTQUFTLEdBQUcsR0FBRyxTQUFTLE9BQU8sRUFBRSxFQUMzQyxVQUFVLFNBQVMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLEVBQzFDLFVBQVUsR0FBRyx3QkFBd0IsQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsRUFFL0UsSUFBSSxFQUFFLEdBQUcsRUFBRSxFQUNYLE9BQU8sRUFBRSxHQUFHLEVBQ1osR0FBRyxFQUFFLElBQUksRUFDVCxTQUFTLEVBQUUsVUFBVSxFQUNyQixNQUFNLENBQUMsRUFBRSxDQUFDLEdBQUcsRUFBRSxVQUFVLEtBQUssT0FBTyxFQUNyQyxNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsVUFBVSxDQU1aO0FBRUQsd0JBQWdCLGVBQWUsQ0FDN0IsS0FBSyxDQUFDLElBQUksU0FBUyxHQUFHLEdBQUcsU0FBUyxPQUFPLEVBQUUsRUFDM0MsVUFBVSxTQUFTLGlCQUFpQixDQUFDLElBQUksQ0FBQyxFQUMxQyxVQUFVLEdBQUcsd0JBQXdCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxHQUFHLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxDQUFDLEVBRS9FLElBQUksRUFBRSxHQUFHLEVBQUUsRUFDWCxPQUFPLEVBQUUsR0FBRyxFQUNaLEdBQUcsRUFBRSxJQUFJLEVBQ1QsU0FBUyxFQUFFLFVBQVUsRUFDckIsTUFBTSxDQUFDLEVBQUUsQ0FBQyxHQUFHLEVBQUUsVUFBVSxLQUFLLE9BQU8sRUFDckMsTUFBTSxDQUFDLEVBQUUsTUFBTSxHQUNkLFVBQVUsR0FBRyxTQUFTLENBZ0J4QjtBQUVELHdCQUFnQixxQkFBcUIsQ0FBQyxHQUFHLEVBQUUsR0FBRyxPQVc3QztBQUVELHdCQUFnQixTQUFTLENBQUMsSUFBSSxFQUFFLEdBQUcsRUFBRSxHQUFHLEdBQUcsQ0FlMUM7QUEyRUQ7Ozs7O0dBS0c7QUFDSCx3QkFBZ0IsZUFBZSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUUsR0FBRyxHQUFFLEdBQWUsR0FBRyxrQkFBa0IsQ0FrRXBGO0FBeUJEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLHFCQUFxQixDQUFDLElBQUksRUFBRSxHQUFHLEdBQUcsU0FBUyxFQUFFLEdBQUcsRUFBRSxHQUFHLEdBQUcsTUFBTSxHQUFHLFNBQVMsQ0FVekY7QUFFRCx3QkFBZ0IscUJBQXFCLENBQUMsR0FBRyxFQUFFLEdBQUcsc0JBYTdDO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQWdCLGlCQUFpQixDQUFDLEVBQUUsRUFBRSxvQkFBb0IsR0FBRyxFQUFFLElBQUksb0JBQW9CLEdBQUc7SUFDeEYsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0lBQ3pCLG1CQUFtQixFQUFFLFNBQVMsR0FBRyxFQUFFLENBQUM7Q0FDckMsQ0FPQTtBQUVEOztHQUVHO0FBQ0gsd0JBQWdCLG1CQUFtQixDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsRUFBRSxVQUFVLEVBQUUsTUFBTSxHQUFHLE1BQU0sQ0FPaEYifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EACL,KAAK,GAAG,EAGR,KAAK,iBAAiB,EAEtB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,GAAG,EACR,KAAK,GAAG,EAGT,MAAM,MAAM,CAAC;AAGd,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,GAAG,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IAErB,YAAY,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,EAIhD;CACF;AAED,wBAAgB,YAAY,CAC1B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAMZ;AAED,wBAAgB,eAAe,CAC7B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,GAAG,SAAS,CAgBxB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,OAW7C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAe1C;AA2ED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,GAAE,GAAe,GAAG,kBAAkB,CA4DpF;AAyBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,GAAG,SAAS,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAUzF;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,sBAa7C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,oBAAoB,GAAG,EAAE,IAAI,oBAAoB,GAAG;IACxF,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,SAAS,GAAG,EAAE,CAAC;CACrC,CAOA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAOhF"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EACL,KAAK,GAAG,EAGR,KAAK,iBAAiB,EAEtB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,GAAG,EACR,KAAK,GAAG,EAGT,MAAM,MAAM,CAAC;AAGd,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,GAAG,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IAErB,YAAY,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,EAIxE;CACF;AAED,wBAAgB,YAAY,CAC1B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAMZ;AAED,wBAAgB,eAAe,CAC7B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,GAAG,SAAS,CAgBxB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,OAW7C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAe1C;AA2ED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,GAAE,GAAe,GAAG,kBAAkB,CAkEpF;AAyBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,GAAG,SAAS,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAUzF;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,sBAa7C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,oBAAoB,GAAG,EAAE,IAAI,oBAAoB,GAAG;IACxF,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,SAAS,GAAG,EAAE,CAAC;CACrC,CAOA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAOhF"}
package/dest/utils.js CHANGED
@@ -3,8 +3,8 @@ import { BaseError, ContractFunctionRevertedError, decodeErrorResult, decodeEven
3
3
  import { formatAbiItem, formatAbiParams } from 'viem/utils';
4
4
  export class FormattedViemError extends Error {
5
5
  metaMessages;
6
- constructor(message, metaMessages){
7
- super(message);
6
+ constructor(message, metaMessages, options){
7
+ super(message, options);
8
8
  this.name = 'FormattedViemError';
9
9
  this.metaMessages = metaMessages;
10
10
  }
@@ -135,6 +135,7 @@ function getNestedErrorData(error) {
135
135
  if (error instanceof FormattedViemError) {
136
136
  return error;
137
137
  }
138
+ const originalError = error;
138
139
  // First try to decode as a custom error using the ABI
139
140
  try {
140
141
  const data = getNestedErrorData(error);
@@ -145,7 +146,9 @@ function getNestedErrorData(error) {
145
146
  data: data
146
147
  });
147
148
  if (decoded) {
148
- return new FormattedViemError(`${decoded.errorName}(${decoded.args?.join(', ') ?? ''})`, error?.metaMessages);
149
+ return new FormattedViemError(`${decoded.errorName}(${decoded.args?.join(', ') ?? ''})`, error?.metaMessages, {
150
+ cause: originalError
151
+ });
149
152
  }
150
153
  }
151
154
  // If it's a BaseError, try to get the custom error through ContractFunctionRevertedError
@@ -157,7 +160,9 @@ function getNestedErrorData(error) {
157
160
  errorName = revertError.signature ?? '';
158
161
  }
159
162
  const args = revertError.metaMessages && revertError.metaMessages?.length > 1 ? revertError.metaMessages[1].trimStart() : '';
160
- return new FormattedViemError(`${errorName}${args}`, error?.metaMessages);
163
+ return new FormattedViemError(`${errorName}${args}`, error?.metaMessages, {
164
+ cause: originalError
165
+ });
161
166
  }
162
167
  }
163
168
  } catch {
@@ -175,9 +180,13 @@ function getNestedErrorData(error) {
175
180
  }
176
181
  // If it's a regular Error instance, return it with its message
177
182
  if (error instanceof Error) {
178
- return new FormattedViemError(truncateErrorMessage(error.message), error?.metaMessages);
183
+ return new FormattedViemError(truncateErrorMessage(error.message), error?.metaMessages, {
184
+ cause: originalError
185
+ });
179
186
  }
180
- return new FormattedViemError(truncateErrorMessage(String(error)));
187
+ return new FormattedViemError(truncateErrorMessage(String(error)), undefined, {
188
+ cause: originalError
189
+ });
181
190
  }
182
191
  function stripAbis(obj) {
183
192
  if (!obj || typeof obj !== 'object') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/ethereum",
3
- "version": "0.0.1-commit.993d240",
3
+ "version": "0.0.1-commit.9a89641",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./account": "./dest/account.js",
@@ -40,7 +40,6 @@
40
40
  "build": "yarn clean && ../scripts/tsc.sh",
41
41
  "build:dev": "../scripts/tsc.sh --watch",
42
42
  "clean": "rm -rf ./dest .tsbuildinfo",
43
- "generate": "./scripts/generate.sh",
44
43
  "start:dev": "concurrently -k \"../scripts/tsc.sh --watch\" \"nodemon --watch dest --exec yarn start\"",
45
44
  "start": "node ./dest/index.js",
46
45
  "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}",
@@ -50,10 +49,10 @@
50
49
  "../package.common.json"
51
50
  ],
52
51
  "dependencies": {
53
- "@aztec/blob-lib": "0.0.1-commit.993d240",
54
- "@aztec/constants": "0.0.1-commit.993d240",
55
- "@aztec/foundation": "0.0.1-commit.993d240",
56
- "@aztec/l1-artifacts": "0.0.1-commit.993d240",
52
+ "@aztec/blob-lib": "0.0.1-commit.9a89641",
53
+ "@aztec/constants": "0.0.1-commit.9a89641",
54
+ "@aztec/foundation": "0.0.1-commit.9a89641",
55
+ "@aztec/l1-artifacts": "6.0.0-nightly.20260807",
57
56
  "dotenv": "^16.0.3",
58
57
  "lodash.chunk": "^4.2.0",
59
58
  "lodash.pickby": "^4.5.0",
package/src/client.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import type { Logger } from '@aztec/foundation/log';
2
2
  import { retryUntil } from '@aztec/foundation/retry';
3
+ import { getErrorCause } from '@aztec/foundation/types';
3
4
 
4
5
  import {
5
6
  type Chain,
7
+ type FallbackTransport,
6
8
  type HDAccount,
9
+ HttpRequestError,
10
+ type HttpTransport,
7
11
  type LocalAccount,
8
12
  type PrivateKeyAccount,
9
13
  createPublicClient,
@@ -31,9 +35,43 @@ type Config = {
31
35
 
32
36
  export type { Config as EthereumClientConfig };
33
37
 
38
+ /** Error exposed by L1 RPC transports without including provider URLs in its message. */
39
+ export class L1RpcError extends Error {
40
+ constructor(message: string, options?: ErrorOptions) {
41
+ super(message, options);
42
+ this.name = 'L1RpcError';
43
+ }
44
+ }
45
+
34
46
  /** Creates a viem fallback HTTP transport for the given L1 RPC URLs. */
35
47
  export function makeL1HttpTransport(rpcUrls: string[], opts?: { timeout?: number }) {
36
- return fallback(rpcUrls.map(url => http(url, { batch: false, timeout: opts?.timeout })));
48
+ return wrapL1RpcTransport(fallback(rpcUrls.map(url => http(url, { batch: false, timeout: opts?.timeout }))));
49
+ }
50
+
51
+ /** Returns the HTTP status from an L1 RPC error's cause chain, if one is available. */
52
+ export function getL1RpcHttpStatus(err: unknown): number | undefined {
53
+ return getErrorCause(err, HttpRequestError)?.status;
54
+ }
55
+
56
+ /** Returns true when an L1 RPC error's cause chain contains the given HTTP status. */
57
+ export function isL1RpcHttpStatus(err: unknown, status: number): boolean {
58
+ return getL1RpcHttpStatus(err) === status;
59
+ }
60
+
61
+ function wrapL1RpcTransport(transport: FallbackTransport<HttpTransport[]>): FallbackTransport<HttpTransport[]> {
62
+ const wrappedTransport: FallbackTransport<HttpTransport[]> = parameters => {
63
+ const fallbackTransport = transport(parameters);
64
+ const request: typeof fallbackTransport.request = async args => {
65
+ try {
66
+ return await fallbackTransport.request(args);
67
+ } catch (err) {
68
+ throw err instanceof L1RpcError ? err : new L1RpcError('L1 RPC request failed', { cause: err });
69
+ }
70
+ };
71
+ return { ...fallbackTransport, request };
72
+ };
73
+
74
+ return wrappedTransport;
37
75
  }
38
76
 
39
77
  /**
package/src/config.ts CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  optionalNumberConfigHelper,
10
10
  } from '@aztec/foundation/config';
11
11
  import { EthAddress } from '@aztec/foundation/eth-address';
12
+ import l1ContractsDefaultEnv from '@aztec/l1-artifacts/network-defaults.json' with { type: 'json' };
12
13
 
13
- import { l1ContractsDefaultEnv } from './generated/l1-contracts-defaults.js';
14
14
  import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from './l1_tx_utils/config.js';
15
15
 
16
16
  export type GenesisStateConfig = {
@@ -81,11 +81,21 @@ export type L1ContractsConfig = {
81
81
  initialEthPerFeeAsset: bigint;
82
82
  /** The number of seconds to wait for an exit */
83
83
  exitDelaySeconds: number;
84
+ /** Validator set size at or below which the entry queue uses the bootstrap flush size. */
85
+ entryQueueBootstrapValidatorSetSize: number;
86
+ /** Number of validators admitted from the entry queue per flush during the bootstrap phase. */
87
+ entryQueueBootstrapFlushSize: number;
88
+ /** Minimum number of validators admitted from the entry queue per flush. */
89
+ entryQueueFlushSizeMin: number;
90
+ /** Divisor applied to the queue size to derive the normal per-flush admission count. */
91
+ entryQueueFlushSizeQuotient: number;
92
+ /** Maximum number of validators admitted from the entry queue per flush. */
93
+ entryQueueMaxFlushSize: number;
84
94
  } & L1TxUtilsConfig;
85
95
 
86
96
  /**
87
97
  * Config mappings for L1ContractsConfig.
88
- * Default values come from generated l1-contracts-defaults.json (source: defaults.yml).
98
+ * Default values come from l1-contracts/scripts/network-defaults.json (published via @aztec/l1-artifacts).
89
99
  * Real deployments use forge scripts which require explicit env vars (vm.envUint).
90
100
  */
91
101
  export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> = {
@@ -237,15 +247,75 @@ export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> =
237
247
  description: 'The delay before a validator can exit the set',
238
248
  ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_EXIT_DELAY_SECONDS),
239
249
  },
250
+ entryQueueBootstrapValidatorSetSize: {
251
+ env: 'AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE',
252
+ description: 'Validator set size at or below which the entry queue uses the bootstrap flush size.',
253
+ ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE),
254
+ },
255
+ entryQueueBootstrapFlushSize: {
256
+ env: 'AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE',
257
+ description: 'Number of validators admitted from the entry queue per flush during the bootstrap phase.',
258
+ ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE),
259
+ },
260
+ entryQueueFlushSizeMin: {
261
+ env: 'AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN',
262
+ description: 'Minimum number of validators admitted from the entry queue per flush.',
263
+ ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN),
264
+ },
265
+ entryQueueFlushSizeQuotient: {
266
+ env: 'AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT',
267
+ description: 'Divisor applied to the queue size to derive the normal per-flush admission count.',
268
+ ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT),
269
+ },
270
+ entryQueueMaxFlushSize: {
271
+ env: 'AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE',
272
+ description: 'Maximum number of validators admitted from the entry queue per flush.',
273
+ ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE),
274
+ },
240
275
  ...omitConfigMappings(l1TxUtilsConfigMappings, ['ethereumSlotDuration']),
241
276
  };
242
277
 
243
278
  /**
244
279
  * Default L1 contracts configuration derived from l1ContractsConfigMappings.
245
- * Source of truth: spartan/environments/defaults.yml -> defaults.l1-contracts
280
+ * Source of truth: l1-contracts/scripts/network-defaults.json (published via @aztec/l1-artifacts).
246
281
  */
247
282
  export const DefaultL1ContractsConfig = getDefaultConfig(l1ContractsConfigMappings);
248
283
 
284
+ /**
285
+ * Validates that `ethereumSlotDuration` and `aztecSlotDuration` are positive and that the L2 slot is an exact
286
+ * multiple of the L1 slot. Every L2 slot boundary must land on an L1 slot boundary; a non-multiple pairing
287
+ * desyncs the epoch/checkpoint timing math throughout the sequencer, validator client, and timetables. Returns
288
+ * a list of error messages (empty when valid).
289
+ */
290
+ export function validateSlotDurations(
291
+ config: Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'>,
292
+ ): string[] {
293
+ const errors: string[] = [];
294
+ if (config.ethereumSlotDuration <= 0) {
295
+ errors.push(`ethereumSlotDuration must be positive (got ${config.ethereumSlotDuration})`);
296
+ }
297
+ if (config.aztecSlotDuration <= 0) {
298
+ errors.push(`aztecSlotDuration must be positive (got ${config.aztecSlotDuration})`);
299
+ }
300
+ if (config.ethereumSlotDuration > 0 && config.aztecSlotDuration % config.ethereumSlotDuration !== 0) {
301
+ errors.push(
302
+ `aztecSlotDuration (${config.aztecSlotDuration}s) must be a multiple of ethereumSlotDuration ` +
303
+ `(${config.ethereumSlotDuration}s)`,
304
+ );
305
+ }
306
+ return errors;
307
+ }
308
+
309
+ /** Throws if {@link validateSlotDurations} reports any errors. */
310
+ export function assertValidSlotDurations(
311
+ config: Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'>,
312
+ ): void {
313
+ const errors = validateSlotDurations(config);
314
+ if (errors.length > 0) {
315
+ throw new Error(`Invalid slot duration configuration:\n${errors.map(e => ` - ${e}`).join('\n')}`);
316
+ }
317
+ }
318
+
249
319
  export const genesisStateConfigMappings: ConfigMappingsType<GenesisStateConfig> = {
250
320
  testAccounts: {
251
321
  env: 'TEST_ACCOUNTS',
@@ -37,7 +37,7 @@ export type L1GovernanceContractAddresses = Pick<
37
37
  'governanceAddress' | 'rollupAddress' | 'registryAddress' | 'governanceProposerAddress'
38
38
  >;
39
39
 
40
- // NOTE: Must be kept in sync with DataStructures.ProposalState in l1-contracts
40
+ // NOTE: Must be kept in sync with IGovernance.ProposalState in l1-contracts
41
41
  export enum ProposalState {
42
42
  Pending,
43
43
  Active,
@@ -45,10 +45,21 @@ export enum ProposalState {
45
45
  Executable,
46
46
  Rejected,
47
47
  Executed,
48
+ Droppable,
48
49
  Dropped,
49
50
  Expired,
50
51
  }
51
52
 
53
+ /**
54
+ * Outcome of {@link ReadOnlyGovernanceContract.getPayloadProposalStatus} for a queried payload.
55
+ * - `'live'`: a proposal referencing the payload is still progressing (Pending/Active/Queued/
56
+ * Executable) or is `Droppable`, so signalling for it again is redundant or premature.
57
+ * - `'executed'`: no live proposal references the payload, but one was already executed within the
58
+ * bounded lookback (or a prior sweep observed the execution and memoized it).
59
+ * - `'none'`: no live or executed proposal references the payload within the bounded lookback.
60
+ */
61
+ export type PayloadProposalStatus = 'live' | 'executed' | 'none';
62
+
52
63
  /** Vote tallies on a single proposal. Both fields are mutated by `Governance.vote`. */
53
64
  export interface Ballot {
54
65
  yea: bigint;
@@ -119,6 +130,17 @@ const TERMINAL_PROPOSAL_STATES: ReadonlySet<ProposalState> = new Set([
119
130
  ProposalState.Expired,
120
131
  ]);
121
132
 
133
+ // Set of `ProposalState` values in which a proposal is still progressing towards execution.
134
+ // `Droppable` is deliberately excluded: it is neither live (it cannot progress on its own) nor
135
+ // terminal (it can resume its lifecycle if the governanceProposer is restored), so it is handled
136
+ // separately and is never memoized as immutable.
137
+ const LIVE_PROPOSAL_STATES: ReadonlySet<ProposalState> = new Set([
138
+ ProposalState.Pending,
139
+ ProposalState.Active,
140
+ ProposalState.Queued,
141
+ ProposalState.Executable,
142
+ ]);
143
+
122
144
  // Hard upper bound on the wall-clock lifetime of any Governance proposal, in seconds.
123
145
  // Each proposal stores its own snapshot of `ProposalConfiguration` at creation time and progresses
124
146
  // through Pending -> Active -> Queued -> Executable using those frozen durations
@@ -173,6 +195,14 @@ export class ReadOnlyGovernanceContract {
173
195
  */
174
196
  private readonly originalPayloadCache: Map<Hex, Hex | undefined> = new Map();
175
197
 
198
+ /**
199
+ * Payloads (lowercased hex) observed as the subject of an `Executed` Governance proposal. Execution
200
+ * is immutable on-chain, so this verdict never expires within a process. It restores the
201
+ * `'executed'` classification for payloads whose executed proposal has since aged past the bounded
202
+ * lookback below. Lost on restart, at which point the bounded-lookback limitation applies again.
203
+ */
204
+ private readonly executedPayloads: Set<string> = new Set();
205
+
176
206
  constructor(
177
207
  address: Hex,
178
208
  public readonly client: ViemClient,
@@ -263,30 +293,38 @@ export class ReadOnlyGovernanceContract {
263
293
  }
264
294
 
265
295
  /**
266
- * Checks whether the given original payload is currently the subject of a live (non-terminal)
267
- * Governance proposal. Returns true only if a proposal references this payload and is still in
268
- * Pending, Active, Queued, or Executable state. Terminal proposals (Executed, Rejected, Dropped,
269
- * Expired) are ignored, because once a proposal reaches a terminal state the same original
270
- * payload may legitimately be re-signaled and re-submitted via the GovernanceProposer (each round
271
- * is independent and there is no payload-level uniqueness check on-chain).
296
+ * Classifies the given original payload against the Governance proposal history. Distinguishes a
297
+ * payload that is still the subject of a live proposal (`'live'`) from one whose proposal was
298
+ * already executed (`'executed'`) from one that has no relevant proposal (`'none'`), so callers can
299
+ * stop re-signalling an executed payload while still re-signalling one whose proposal was merely
300
+ * rejected/dropped/expired (each GovernanceProposer round is independent and there is no
301
+ * payload-level uniqueness check on-chain).
302
+ *
303
+ * A proposal matches the payload either directly (its stored `payload` equals the target, as for
304
+ * `proposeWithLock` proposals) or via its `GSEPayload` wrapper unwrapping to the target. `'live'`
305
+ * (including `Droppable`) takes precedence over `'executed'`, so a payload re-submitted while a
306
+ * prior execution is still in the lookback window reads as `'live'`.
272
307
  *
273
308
  * Implemented as a bounded view-call sweep over `Governance.proposals` rather than an event scan,
274
309
  * because `eth_getLogs` over the full deployment history of a long-lived rollup exceeds typical
275
310
  * RPC block-range caps. The number of proposals (`proposalCount`) is small in practice, and we
276
- * walk newest -> oldest with a hard early-stop on the protocol-wide proposal lifetime cap.
311
+ * walk newest -> oldest with a hard early-stop on the protocol-wide proposal lifetime cap. This
312
+ * makes the `'executed'` verdict a *bounded lookback* rather than permanent suppression: a proposal
313
+ * executed longer ago than the lifetime cap is only reported once it has been observed and
314
+ * memoized in-process (memo is lost on restart).
277
315
  */
278
- public async hasActiveProposalWithPayload(payload: Hex): Promise<boolean> {
316
+ public async getPayloadProposalStatus(payload: Hex): Promise<PayloadProposalStatus> {
317
+ const target = payload.toLowerCase() as Hex;
318
+
279
319
  const proposalCount = await this.getProposalCount();
280
320
  if (proposalCount === 0n) {
281
- return false;
321
+ return this.executedPayloads.has(target) ? 'executed' : 'none';
282
322
  }
283
323
 
284
324
  // Anything created before this cutoff is guaranteed terminal regardless of its frozen config.
285
325
  const block = await this.client.getBlock();
286
326
  const hardCutoff = block.timestamp - MAX_PROPOSAL_LIFETIME_SECONDS;
287
327
 
288
- const target = payload.toLowerCase() as Hex;
289
-
290
328
  // Proposals are append-only with monotonically non-decreasing creation timestamps, so iterating
291
329
  // from newest -> oldest lets us early-stop as soon as we cross the lifetime cutoff.
292
330
  for (let id = proposalCount - 1n; id >= 0n; id--) {
@@ -294,24 +332,29 @@ export class ReadOnlyGovernanceContract {
294
332
 
295
333
  // Hard early-stop: every older proposal is also older than the cutoff and therefore terminal.
296
334
  if (proposal.creation < hardCutoff) {
297
- return false;
335
+ break;
298
336
  }
299
337
 
338
+ const proposalPayload = proposal.payload.toString().toLowerCase();
300
339
  const original = await this.getOriginalPayload(proposal.payload);
301
- if (original === undefined || original.toLowerCase() !== target) {
340
+ const matches = proposalPayload === target || (original !== undefined && original.toLowerCase() === target);
341
+ if (!matches) {
302
342
  continue;
303
343
  }
304
344
 
305
- // The wrapper unwraps to our payload. Only treat this as "already proposed" if the proposal
306
- // is still live -- terminal states allow re-proposing the same payload in a later round.
307
- if (TERMINAL_PROPOSAL_STATES.has(proposal.state)) {
308
- continue;
345
+ // A live or Droppable proposal blocks re-signalling and wins over any executed one.
346
+ if (LIVE_PROPOSAL_STATES.has(proposal.state) || proposal.state === ProposalState.Droppable) {
347
+ return 'live';
348
+ }
349
+
350
+ if (proposal.state === ProposalState.Executed) {
351
+ this.executedPayloads.add(target);
309
352
  }
310
353
 
311
- return true;
354
+ // Rejected/Dropped/Expired proposals allow re-proposing the same payload; keep scanning.
312
355
  }
313
356
 
314
- return false;
357
+ return this.executedPayloads.has(target) ? 'executed' : 'none';
315
358
  }
316
359
 
317
360
  /**
@@ -15,7 +15,7 @@ import {
15
15
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
16
16
  import type { ViemClient } from '../types.js';
17
17
  import { type IEmpireBase, encodeSignal, encodeSignalWithSignature, signSignalWithSig } from './empire_base.js';
18
- import { ReadOnlyGovernanceContract, extractProposalIdFromLogs } from './governance.js';
18
+ import { type PayloadProposalStatus, ReadOnlyGovernanceContract, extractProposalIdFromLogs } from './governance.js';
19
19
 
20
20
  export class GovernanceProposerContract implements IEmpireBase {
21
21
  private readonly proposer: GetContractReturnType<typeof GovernanceProposerAbi, ViemClient>;
@@ -38,16 +38,16 @@ export class GovernanceProposerContract implements IEmpireBase {
38
38
  this.proposer = getContract({ address, abi: GovernanceProposerAbi, client });
39
39
  }
40
40
 
41
- public get address() {
41
+ public get address(): EthAddress {
42
42
  return EthAddress.fromString(this.proposer.address);
43
43
  }
44
44
 
45
- public async getRollupAddress() {
45
+ public async getRollupAddress(): Promise<EthAddress> {
46
46
  return EthAddress.fromString(await this.proposer.read.getInstance());
47
47
  }
48
48
 
49
49
  @memoize
50
- public async getRegistryAddress() {
50
+ public async getRegistryAddress(): Promise<EthAddress> {
51
51
  return EthAddress.fromString(await this.proposer.read.REGISTRY());
52
52
  }
53
53
 
@@ -59,10 +59,6 @@ export class GovernanceProposerContract implements IEmpireBase {
59
59
  return this.proposer.read.ROUND_SIZE();
60
60
  }
61
61
 
62
- public getInstance() {
63
- return this.proposer.read.getInstance();
64
- }
65
-
66
62
  public computeRound(slot: SlotNumber): Promise<bigint> {
67
63
  return this.proposer.read.computeRound([BigInt(slot)]);
68
64
  }
@@ -107,7 +103,7 @@ export class GovernanceProposerContract implements IEmpireBase {
107
103
  signer,
108
104
  payload,
109
105
  slot,
110
- await this.getInstance(),
106
+ (await this.getRollupAddress()).toString(),
111
107
  this.address.toString(),
112
108
  chainId,
113
109
  );
@@ -130,15 +126,15 @@ export class GovernanceProposerContract implements IEmpireBase {
130
126
  }
131
127
 
132
128
  /**
133
- * Returns true iff the given original payload is currently the subject of a live (non-terminal)
134
- * Governance proposal. Delegates to `ReadOnlyGovernanceContract.hasActiveProposalWithPayload`, which
135
- * implements the actual sweep against the Governance contract -- this method exists only as a
136
- * convenience wrapper so callers that already hold a GovernanceProposer reference don't have to
129
+ * Classifies the given original payload against the Governance proposal history (`'live'` /
130
+ * `'executed'` / `'none'`). Delegates to `ReadOnlyGovernanceContract.getPayloadProposalStatus`,
131
+ * which implements the actual sweep against the Governance contract -- this method exists only as
132
+ * a convenience wrapper so callers that already hold a GovernanceProposer reference don't have to
137
133
  * resolve the Governance address themselves.
138
134
  */
139
- public async hasActiveProposalWithPayload(payload: Hex): Promise<boolean> {
135
+ public async getPayloadProposalStatus(payload: Hex): Promise<PayloadProposalStatus> {
140
136
  const governance = await this.getGovernance();
141
- return governance.hasActiveProposalWithPayload(payload);
137
+ return governance.getPayloadProposalStatus(payload);
142
138
  }
143
139
 
144
140
  /**
@@ -47,11 +47,28 @@ export class GSEContract {
47
47
  return EthAddress.fromString(await this.gse.read.getGovernance());
48
48
  }
49
49
 
50
- getAttestersFromIndicesAtTime(instance: Hex | EthAddress, ts: bigint, indices: bigint[]) {
50
+ getAttestersFromIndicesAtTime(
51
+ instance: Hex | EthAddress,
52
+ ts: bigint,
53
+ indices: bigint[],
54
+ options?: { blockNumber?: bigint },
55
+ ) {
56
+ if (instance instanceof EthAddress) {
57
+ instance = instance.toString();
58
+ }
59
+ return this.gse.read.getAttestersFromIndicesAtTime([instance, ts, indices], options);
60
+ }
61
+
62
+ /** Number of attesters registered to `instance` as of the given L1 timestamp. */
63
+ public async getAttesterCountAtTime(
64
+ instance: Hex | EthAddress,
65
+ ts: bigint,
66
+ options?: { blockNumber?: bigint },
67
+ ): Promise<number> {
51
68
  if (instance instanceof EthAddress) {
52
69
  instance = instance.toString();
53
70
  }
54
- return this.gse.read.getAttestersFromIndicesAtTime([instance, ts, indices]);
71
+ return Number(await this.gse.read.getAttesterCountAtTime([instance, ts], options));
55
72
  }
56
73
 
57
74
  public async getRegistrationDigest(publicKey: ProjPointType<bigint>): Promise<ProjPointType<bigint>> {