@aztec/ethereum 0.0.1-commit.3469e52 → 0.0.1-commit.350e0a4d

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 (183) hide show
  1. package/dest/client.d.ts +25 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +60 -8
  4. package/dest/config.d.ts +31 -8
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +74 -14
  7. package/dest/contracts/chain_state_override.d.ts +78 -0
  8. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  9. package/dest/contracts/chain_state_override.js +137 -0
  10. package/dest/contracts/fee_asset_handler.d.ts +1 -1
  11. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_handler.js +2 -0
  13. package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
  14. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  15. package/dest/contracts/fee_asset_price_oracle.js +656 -0
  16. package/dest/contracts/governance.d.ts +110 -33
  17. package/dest/contracts/governance.d.ts.map +1 -1
  18. package/dest/contracts/governance.js +207 -16
  19. package/dest/contracts/governance_proposer.d.ts +33 -1
  20. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  21. package/dest/contracts/governance_proposer.js +57 -3
  22. package/dest/contracts/gse.d.ts +4 -2
  23. package/dest/contracts/gse.d.ts.map +1 -1
  24. package/dest/contracts/gse.js +2 -2
  25. package/dest/contracts/inbox.d.ts +3 -3
  26. package/dest/contracts/inbox.d.ts.map +1 -1
  27. package/dest/contracts/inbox.js +12 -7
  28. package/dest/contracts/index.d.ts +4 -3
  29. package/dest/contracts/index.d.ts.map +1 -1
  30. package/dest/contracts/index.js +3 -2
  31. package/dest/contracts/multicall.d.ts +130 -13
  32. package/dest/contracts/multicall.d.ts.map +1 -1
  33. package/dest/contracts/multicall.js +203 -93
  34. package/dest/contracts/outbox.d.ts +27 -4
  35. package/dest/contracts/outbox.d.ts.map +1 -1
  36. package/dest/contracts/outbox.js +34 -7
  37. package/dest/contracts/registry.d.ts +3 -1
  38. package/dest/contracts/registry.d.ts.map +1 -1
  39. package/dest/contracts/registry.js +30 -1
  40. package/dest/contracts/rollup.d.ts +5422 -33
  41. package/dest/contracts/rollup.d.ts.map +1 -1
  42. package/dest/contracts/rollup.js +409 -78
  43. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
  44. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  45. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +18 -13
  46. package/dest/deploy_aztec_l1_contracts.d.ts +21 -12
  47. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  48. package/dest/deploy_aztec_l1_contracts.js +55 -35
  49. package/dest/deploy_l1_contract.js +3 -3
  50. package/dest/l1_artifacts.d.ts +16231 -15758
  51. package/dest/l1_artifacts.d.ts.map +1 -1
  52. package/dest/l1_artifacts.js +9 -24
  53. package/dest/l1_contract_addresses.d.ts +61 -65
  54. package/dest/l1_contract_addresses.d.ts.map +1 -1
  55. package/dest/l1_contract_addresses.js +101 -82
  56. package/dest/l1_reader.d.ts +5 -5
  57. package/dest/l1_reader.d.ts.map +1 -1
  58. package/dest/l1_reader.js +9 -7
  59. package/dest/l1_tx_utils/config.d.ts +7 -1
  60. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  61. package/dest/l1_tx_utils/config.js +17 -4
  62. package/dest/l1_tx_utils/constants.d.ts +1 -1
  63. package/dest/l1_tx_utils/constants.js +2 -2
  64. package/dest/l1_tx_utils/factory.d.ts +18 -10
  65. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  66. package/dest/l1_tx_utils/factory.js +17 -7
  67. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  68. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  69. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  70. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  71. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  72. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  73. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/index-blobs.js +2 -2
  75. package/dest/l1_tx_utils/index.d.ts +2 -1
  76. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  77. package/dest/l1_tx_utils/index.js +1 -0
  78. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +23 -4
  79. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -1
  80. package/dest/l1_tx_utils/l1_fee_analyzer.js +65 -3
  81. package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -7
  82. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  83. package/dest/l1_tx_utils/l1_tx_utils.js +185 -88
  84. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  85. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  86. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +13 -8
  87. package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
  88. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  89. package/dest/{test → l1_tx_utils}/tx_delayer.js +67 -37
  90. package/dest/l1_tx_utils/types.d.ts +27 -1
  91. package/dest/l1_tx_utils/types.d.ts.map +1 -1
  92. package/dest/l1_tx_utils/types.js +10 -0
  93. package/dest/publisher_manager.d.ts +34 -7
  94. package/dest/publisher_manager.d.ts.map +1 -1
  95. package/dest/publisher_manager.js +119 -8
  96. package/dest/queries.d.ts +13 -2
  97. package/dest/queries.d.ts.map +1 -1
  98. package/dest/queries.js +70 -4
  99. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  100. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  101. package/dest/test/blob_kzg_warmup.js +64 -0
  102. package/dest/test/chain_monitor.d.ts +55 -4
  103. package/dest/test/chain_monitor.d.ts.map +1 -1
  104. package/dest/test/chain_monitor.js +104 -3
  105. package/dest/test/eth_cheat_codes.d.ts +37 -6
  106. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  107. package/dest/test/eth_cheat_codes.js +89 -41
  108. package/dest/test/index.d.ts +2 -3
  109. package/dest/test/index.d.ts.map +1 -1
  110. package/dest/test/index.js +1 -2
  111. package/dest/test/rollup_cheat_codes.d.ts +63 -4
  112. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  113. package/dest/test/rollup_cheat_codes.js +96 -8
  114. package/dest/test/start_anvil.d.ts +23 -3
  115. package/dest/test/start_anvil.d.ts.map +1 -1
  116. package/dest/test/start_anvil.js +143 -29
  117. package/dest/test/upgrade_utils.js +2 -2
  118. package/dest/utils.d.ts +10 -2
  119. package/dest/utils.d.ts.map +1 -1
  120. package/dest/utils.js +94 -16
  121. package/package.json +6 -9
  122. package/src/client.ts +66 -2
  123. package/src/config.ts +104 -18
  124. package/src/contracts/chain_state_override.ts +200 -0
  125. package/src/contracts/fee_asset_handler.ts +2 -0
  126. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  127. package/src/contracts/governance.ts +279 -13
  128. package/src/contracts/governance_proposer.ts +58 -2
  129. package/src/contracts/gse.ts +7 -2
  130. package/src/contracts/inbox.ts +13 -5
  131. package/src/contracts/index.ts +3 -2
  132. package/src/contracts/multicall.ts +247 -103
  133. package/src/contracts/outbox.ts +42 -8
  134. package/src/contracts/registry.ts +31 -1
  135. package/src/contracts/rollup.ts +461 -82
  136. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +16 -14
  137. package/src/deploy_aztec_l1_contracts.ts +77 -43
  138. package/src/deploy_l1_contract.ts +3 -3
  139. package/src/l1_artifacts.ts +12 -35
  140. package/src/l1_contract_addresses.ts +120 -94
  141. package/src/l1_reader.ts +17 -10
  142. package/src/l1_tx_utils/config.ts +23 -3
  143. package/src/l1_tx_utils/constants.ts +2 -2
  144. package/src/l1_tx_utils/factory.ts +31 -31
  145. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  146. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  147. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  148. package/src/l1_tx_utils/index-blobs.ts +2 -2
  149. package/src/l1_tx_utils/index.ts +1 -0
  150. package/src/l1_tx_utils/l1_fee_analyzer.ts +75 -3
  151. package/src/l1_tx_utils/l1_tx_utils.ts +170 -68
  152. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +13 -8
  153. package/src/{test → l1_tx_utils}/tx_delayer.ts +93 -56
  154. package/src/l1_tx_utils/types.ts +32 -0
  155. package/src/publisher_manager.ts +145 -12
  156. package/src/queries.ts +77 -4
  157. package/src/test/blob_kzg_warmup.ts +65 -0
  158. package/src/test/chain_monitor.ts +148 -3
  159. package/src/test/eth_cheat_codes.ts +82 -45
  160. package/src/test/index.ts +1 -2
  161. package/src/test/rollup_cheat_codes.ts +126 -8
  162. package/src/test/start_anvil.ts +177 -29
  163. package/src/test/upgrade_utils.ts +2 -2
  164. package/src/utils.ts +98 -18
  165. package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
  166. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  167. package/dest/contracts/empire_slashing_proposer.js +0 -209
  168. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  169. package/dest/generated/l1-contracts-defaults.d.ts +0 -29
  170. package/dest/generated/l1-contracts-defaults.d.ts.map +0 -1
  171. package/dest/generated/l1-contracts-defaults.js +0 -29
  172. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  173. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  174. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  175. package/dest/test/delayed_tx_utils.d.ts +0 -13
  176. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  177. package/dest/test/delayed_tx_utils.js +0 -28
  178. package/dest/test/tx_delayer.d.ts +0 -36
  179. package/dest/test/tx_delayer.d.ts.map +0 -1
  180. package/src/contracts/empire_slashing_proposer.ts +0 -270
  181. package/src/generated/l1-contracts-defaults.ts +0 -31
  182. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  183. package/src/test/delayed_tx_utils.ts +0 -52
@@ -4,7 +4,9 @@ import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { memoize } from '@aztec/foundation/decorators';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
+ import { createLogger } from '@aztec/foundation/log';
7
8
  import { makeBackoff, retry } from '@aztec/foundation/retry';
9
+ import { getErrorCause } from '@aztec/foundation/types';
8
10
  import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
9
11
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
10
12
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
@@ -12,10 +14,15 @@ import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
12
14
  import chunk from 'lodash.chunk';
13
15
  import {
14
16
  type Account,
17
+ ContractFunctionRevertedError,
15
18
  type GetContractReturnType,
16
19
  type Hex,
20
+ type Log,
21
+ RpcRequestError,
17
22
  type StateOverride,
18
23
  type WatchContractEventReturnType,
24
+ decodeErrorResult,
25
+ encodeAbiParameters,
19
26
  encodeFunctionData,
20
27
  getContract,
21
28
  hexToBigInt,
@@ -29,11 +36,10 @@ import type { L1ReaderConfig } from '../l1_reader.js';
29
36
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
30
37
  import type { ViemClient } from '../types.js';
31
38
  import { formatViemError } from '../utils.js';
32
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
33
39
  import { GSEContract } from './gse.js';
34
40
  import type { L1EventLog } from './log.js';
35
41
  import { SlasherContract } from './slasher_contract.js';
36
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
42
+ import { SlashingProposerContract } from './slashing_proposer.js';
37
43
  import { checkBlockTag } from './utils.js';
38
44
 
39
45
  export type ViemCommitteeAttestation = {
@@ -55,7 +61,6 @@ export type L1RollupContractAddresses = Pick<
55
61
  | 'feeJuiceAddress'
56
62
  | 'stakingAssetAddress'
57
63
  | 'rewardDistributorAddress'
58
- | 'slashFactoryAddress'
59
64
  | 'gseAddress'
60
65
  >;
61
66
 
@@ -78,6 +83,7 @@ export type ViemHeader = {
78
83
  feeRecipient: `0x${string}`;
79
84
  gasFees: ViemGasFees;
80
85
  totalManaUsed: bigint;
86
+ accumulatedFees: bigint;
81
87
  };
82
88
 
83
89
  export type ViemGasFees = {
@@ -85,12 +91,6 @@ export type ViemGasFees = {
85
91
  feePerL2Gas: bigint;
86
92
  };
87
93
 
88
- export enum SlashingProposerType {
89
- None = 0,
90
- Tally = 1,
91
- Empire = 2,
92
- }
93
-
94
94
  /**
95
95
  * Status of a validator/attester in the staking system.
96
96
  * Matches the Status enum in StakingLib.sol
@@ -108,7 +108,7 @@ export enum AttesterStatus {
108
108
  export type FeeHeader = {
109
109
  excessMana: bigint;
110
110
  manaUsed: bigint;
111
- feeAssetPriceNumerator: bigint;
111
+ ethPerFeeAsset: bigint;
112
112
  congestionCost: bigint;
113
113
  proverCost: bigint;
114
114
  };
@@ -134,6 +134,39 @@ export type L1FeeData = {
134
134
  blobFee: bigint;
135
135
  };
136
136
 
137
+ /** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
138
+ export enum TempCheckpointLogField {
139
+ HeaderHash = 0,
140
+ BlobCommitmentsHash = 1,
141
+ OutHash = 2,
142
+ AttestationsHash = 3,
143
+ PayloadDigest = 4,
144
+ SlotNumber = 5,
145
+ FeeHeader = 6,
146
+ }
147
+
148
+ /**
149
+ * Field-level override input for `tempCheckpointLogs[checkpointNumber]`. Covers the fields the
150
+ * `propose()` path actually reads back. `payloadDigest` is `Buffer32` because it carries an
151
+ * arbitrary `bytes32` value rather than a BN254 scalar. `slotNumber` carries the uint32 portion
152
+ * of the on-chain `CompressedSlot`.
153
+ */
154
+ export type TempCheckpointLogOverrideFields = {
155
+ headerHash?: Fr;
156
+ outHash?: Fr;
157
+ payloadDigest?: Buffer32;
158
+ slotNumber?: SlotNumber;
159
+ feeHeader?: FeeHeader;
160
+ };
161
+
162
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
163
+ export type ManaMinFeeComponents = {
164
+ sequencerCost: bigint;
165
+ proverCost: bigint;
166
+ congestionCost: bigint;
167
+ congestionMultiplier: bigint;
168
+ };
169
+
137
170
  /**
138
171
  * Reward configuration for the rollup
139
172
  */
@@ -193,17 +226,44 @@ export type CheckpointProposedArgs = {
193
226
  checkpointNumber: CheckpointNumber;
194
227
  archive: Fr;
195
228
  versionedBlobHashes: Buffer[];
196
- /** Hash of attestations. Undefined for older events (backwards compatibility). */
197
- attestationsHash?: Buffer32;
198
- /** Digest of the payload. Undefined for older events (backwards compatibility). */
199
- payloadDigest?: Buffer32;
229
+ /** Hash of attestations emitted in the CheckpointProposed event. */
230
+ attestationsHash: Buffer32;
231
+ /** Digest of the payload emitted in the CheckpointProposed event. */
232
+ payloadDigest: Buffer32;
200
233
  };
201
234
 
202
235
  /** Log type for CheckpointProposed events. */
203
236
  export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
204
237
 
238
+ const INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR = 'ValidatorSelection__InsufficientValidatorSetSize';
239
+
240
+ function isValidatorSelectionError(err: unknown, errorName: string): boolean {
241
+ return (
242
+ getErrorCause(err, ContractFunctionRevertedError)?.data?.errorName === errorName ||
243
+ decodeRpcRequestErrorName(err) === errorName
244
+ );
245
+ }
246
+
247
+ function decodeRpcRequestErrorName(err: unknown): string | undefined {
248
+ const data = getErrorCause(err, RpcRequestError)?.data;
249
+ if (!isHexString(data)) {
250
+ return undefined;
251
+ }
252
+
253
+ try {
254
+ return decodeErrorResult({ abi: RollupAbi, data }).errorName;
255
+ } catch {
256
+ return undefined;
257
+ }
258
+ }
259
+
260
+ function isHexString(value: unknown): value is Hex {
261
+ return typeof value === 'string' && value.startsWith('0x');
262
+ }
263
+
205
264
  export class RollupContract {
206
265
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
266
+ private readonly logger = createLogger('ethereum:rollup');
207
267
 
208
268
  private static cachedStfStorageSlot: Hex | undefined;
209
269
  private cachedEscapeHatch?: {
@@ -233,7 +293,7 @@ export class RollupContract {
233
293
 
234
294
  static getFromConfig(config: L1ReaderConfig) {
235
295
  const client = getPublicClient(config);
236
- const address = config.l1Contracts.rollupAddress.toString();
296
+ const address = config.rollupAddress.toString();
237
297
  return new RollupContract(client, address);
238
298
  }
239
299
 
@@ -259,34 +319,18 @@ export class RollupContract {
259
319
  return this.rollup;
260
320
  }
261
321
 
262
- public async getSlashingProposer(): Promise<
263
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
264
- > {
322
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
265
323
  const slasher = await this.getSlasherContract();
266
324
  if (!slasher) {
267
325
  return undefined;
268
326
  }
269
327
 
270
328
  const proposerAddress = await slasher.getProposer();
271
- const proposerAbi = [
272
- {
273
- type: 'function',
274
- name: 'SLASHING_PROPOSER_TYPE',
275
- inputs: [],
276
- outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
277
- stateMutability: 'view',
278
- },
279
- ] as const;
280
-
281
- const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
282
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
283
- if (proposerType === SlashingProposerType.Tally.valueOf()) {
284
- return new TallySlashingProposerContract(this.client, proposerAddress);
285
- } else if (proposerType === SlashingProposerType.Empire.valueOf()) {
286
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
287
- } else {
288
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
329
+ if (proposerAddress.isZero()) {
330
+ return undefined;
289
331
  }
332
+
333
+ return new SlashingProposerContract(this.client, proposerAddress);
290
334
  }
291
335
 
292
336
  @memoize
@@ -379,6 +423,20 @@ export class RollupContract {
379
423
  return Fr.fromString(await this.rollup.read.archiveAt([0n]));
380
424
  }
381
425
 
426
+ @memoize
427
+ async getVkTreeRoot(): Promise<Fr> {
428
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
429
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
430
+ return Fr.fromString(value ?? '0x0');
431
+ }
432
+
433
+ @memoize
434
+ async getProtocolContractsHash(): Promise<Fr> {
435
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
436
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
437
+ return Fr.fromString(value ?? '0x0');
438
+ }
439
+
382
440
  /**
383
441
  * Returns rollup constants used for epoch queries.
384
442
  * Return type is `L1RollupConstants` which is defined in stdlib,
@@ -391,13 +449,25 @@ export class RollupContract {
391
449
  slotDuration: number;
392
450
  epochDuration: number;
393
451
  proofSubmissionEpochs: number;
452
+ targetCommitteeSize: number;
453
+ rollupManaLimit: number;
394
454
  }> {
395
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
455
+ const [
456
+ l1StartBlock,
457
+ l1GenesisTime,
458
+ slotDuration,
459
+ epochDuration,
460
+ proofSubmissionEpochs,
461
+ targetCommitteeSize,
462
+ rollupManaLimit,
463
+ ] = await Promise.all([
396
464
  this.getL1StartBlock(),
397
465
  this.getL1GenesisTime(),
398
466
  this.getSlotDuration(),
399
467
  this.getEpochDuration(),
400
468
  this.getProofSubmissionEpochs(),
469
+ this.getTargetCommitteeSize(),
470
+ this.getManaLimit(),
401
471
  ]);
402
472
  return {
403
473
  l1StartBlock,
@@ -405,6 +475,8 @@ export class RollupContract {
405
475
  slotDuration,
406
476
  epochDuration: Number(epochDuration),
407
477
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
478
+ targetCommitteeSize,
479
+ rollupManaLimit: Number(rollupManaLimit),
408
480
  };
409
481
  }
410
482
 
@@ -459,7 +531,11 @@ export class RollupContract {
459
531
 
460
532
  const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
461
533
  return isOpen;
462
- } catch {
534
+ } catch (err) {
535
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
536
+ epoch: Number(epoch),
537
+ error: err,
538
+ });
463
539
  return false;
464
540
  }
465
541
  }
@@ -479,8 +555,9 @@ export class RollupContract {
479
555
  return EthAddress.fromString(await this.rollup.read.owner());
480
556
  }
481
557
 
482
- async getActiveAttesterCount(): Promise<number> {
483
- return Number(await this.rollup.read.getActiveAttesterCount());
558
+ async getActiveAttesterCount(options?: { blockNumber?: bigint }): Promise<number> {
559
+ await checkBlockTag(options?.blockNumber, this.client);
560
+ return Number(await this.rollup.read.getActiveAttesterCount(options));
484
561
  }
485
562
 
486
563
  public async getSlashingProposerAddress() {
@@ -495,28 +572,43 @@ export class RollupContract {
495
572
  return this.rollup.read.getCheckpointReward();
496
573
  }
497
574
 
498
- async getCheckpointNumber(): Promise<CheckpointNumber> {
499
- return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
575
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
576
+ await checkBlockTag(options?.blockNumber, this.client);
577
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
500
578
  }
501
579
 
502
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
503
- return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber());
580
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
581
+ await checkBlockTag(options?.blockNumber, this.client);
582
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
504
583
  }
505
584
 
506
- async getSlotNumber(): Promise<SlotNumber> {
507
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
585
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
586
+ await checkBlockTag(options?.blockNumber, this.client);
587
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
508
588
  }
509
589
 
510
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
511
- const result = await this.rollup.read.getL1FeesAt([timestamp]);
590
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
591
+ await checkBlockTag(options?.blockNumber, this.client);
592
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
512
593
  return {
513
594
  baseFee: result.baseFee,
514
595
  blobFee: result.blobFee,
515
596
  };
516
597
  }
517
598
 
518
- getFeeAssetPerEth(): Promise<bigint> {
519
- return this.rollup.read.getFeeAssetPerEth();
599
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
600
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
601
+ return {
602
+ excessMana: result.excessMana,
603
+ manaUsed: result.manaUsed,
604
+ ethPerFeeAsset: result.ethPerFeeAsset,
605
+ congestionCost: result.congestionCost,
606
+ proverCost: result.proverCost,
607
+ };
608
+ }
609
+
610
+ getEthPerFeeAsset(): Promise<bigint> {
611
+ return this.rollup.read.getEthPerFeeAsset();
520
612
  }
521
613
 
522
614
  async getCommitteeAt(timestamp: bigint): Promise<EthAddress[] | undefined> {
@@ -528,7 +620,7 @@ export class RollupContract {
528
620
  args: [timestamp],
529
621
  })
530
622
  .catch(e => {
531
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
623
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
532
624
  return { result: undefined };
533
625
  }
534
626
  throw e;
@@ -558,7 +650,7 @@ export class RollupContract {
558
650
  args: [],
559
651
  })
560
652
  .catch(e => {
561
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
653
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
562
654
  return { result: undefined };
563
655
  }
564
656
  throw e;
@@ -589,8 +681,9 @@ export class RollupContract {
589
681
  return EthAddress.fromString(result);
590
682
  }
591
683
 
592
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
593
- const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)]);
684
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
685
+ await checkBlockTag(options?.blockNumber, this.client);
686
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
594
687
  return {
595
688
  archive: Fr.fromString(result.archive),
596
689
  headerHash: Buffer32.fromString(result.headerHash),
@@ -601,7 +694,7 @@ export class RollupContract {
601
694
  feeHeader: {
602
695
  excessMana: result.feeHeader.excessMana,
603
696
  manaUsed: result.feeHeader.manaUsed,
604
- feeAssetPriceNumerator: result.feeHeader.feeAssetPriceNumerator,
697
+ ethPerFeeAsset: result.feeHeader.ethPerFeeAsset,
605
698
  congestionCost: result.feeHeader.congestionCost,
606
699
  proverCost: result.feeHeader.proverCost,
607
700
  },
@@ -623,6 +716,30 @@ export class RollupContract {
623
716
  );
624
717
  }
625
718
 
719
+ /**
720
+ * Returns the effective pending checkpoint, accounting for potential prunes.
721
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
722
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
723
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
724
+ * @param options - Optional L1 block number to pin the queries to.
725
+ */
726
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
727
+ return retry(
728
+ async () => {
729
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
730
+ const canPrune = await this.canPruneAtTime(timestamp, options);
731
+ if (canPrune) {
732
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
733
+ return await this.getCheckpoint(provenCheckpointNumber, options);
734
+ }
735
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
736
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
737
+ },
738
+ 'getting effective pending checkpoint',
739
+ makeBackoff([0.5, 0.5, 0.5]),
740
+ );
741
+ }
742
+
626
743
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
627
744
  const { pending, proven } = await this.rollup.read.getTips();
628
745
  return {
@@ -697,7 +814,7 @@ export class RollupContract {
697
814
  }
698
815
 
699
816
  async getEpochProofPublicInputs(
700
- args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly `0x${string}`[], `0x${string}`],
817
+ args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly ViemHeader[], `0x${string}`],
701
818
  ): Promise<Fr[]> {
702
819
  const result = await this.rollup.read.getEpochProofPublicInputs(args);
703
820
  return result.map(Fr.fromString);
@@ -741,14 +858,13 @@ export class RollupContract {
741
858
  * timestamp of the next L1 block
742
859
  * @throws otherwise
743
860
  */
744
- public async canProposeAtNextEthBlock(
861
+ public async canProposeAt(
745
862
  archive: Buffer,
746
863
  account: `0x${string}` | Account,
747
- slotDuration: number,
748
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
864
+ timestamp: bigint,
865
+ stateOverride: StateOverride = [],
749
866
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
750
- const latestBlock = await this.client.getBlock();
751
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
867
+ const timeOfNextL1Slot = timestamp;
752
868
  const who = typeof account === 'string' ? account : account.address;
753
869
 
754
870
  try {
@@ -760,7 +876,7 @@ export class RollupContract {
760
876
  functionName: 'canProposeAtTime',
761
877
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
762
878
  account,
763
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
879
+ stateOverride,
764
880
  });
765
881
 
766
882
  return {
@@ -774,20 +890,32 @@ export class RollupContract {
774
890
  }
775
891
 
776
892
  /**
777
- * Returns a state override that sets the pending checkpoint number to the specified value. Useful for simulations.
778
- * Requires querying the current state of the contract to get the current proven checkpoint number, as they are both
779
- * stored in the same slot. If the argument is undefined, it returns an empty override.
893
+ * Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
894
+ * Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
895
+ * a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
896
+ * value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
897
+ *
898
+ * Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
899
+ * `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
780
900
  */
781
- public async makePendingCheckpointNumberOverride(
782
- forcePendingCheckpointNumber: CheckpointNumber | undefined,
783
- ): Promise<StateOverride> {
784
- if (forcePendingCheckpointNumber === undefined) {
901
+ public async makeChainTipsOverride(override: {
902
+ pending?: CheckpointNumber;
903
+ proven?: CheckpointNumber;
904
+ }): Promise<StateOverride> {
905
+ if (override.pending === undefined && override.proven === undefined) {
785
906
  return [];
786
907
  }
787
908
  const slot = RollupContract.stfStorageSlot;
788
909
  const currentValue = await this.client.getStorageAt({ address: this.address, slot });
789
- const currentProvenCheckpointNumber = currentValue ? hexToBigInt(currentValue) & ((1n << 128n) - 1n) : 0n;
790
- const newValue = (BigInt(forcePendingCheckpointNumber) << 128n) | currentProvenCheckpointNumber;
910
+ const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
911
+ const currentPending = currentRaw >> 128n;
912
+ const currentProven = currentRaw & ((1n << 128n) - 1n);
913
+ const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
914
+ const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
915
+ if (newProven > newPending) {
916
+ throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
917
+ }
918
+ const newValue = (newPending << 128n) | newProven;
791
919
  return [
792
920
  {
793
921
  address: this.address,
@@ -796,6 +924,168 @@ export class RollupContract {
796
924
  ];
797
925
  }
798
926
 
927
+ /**
928
+ * Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
929
+ * the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
930
+ * pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
931
+ *
932
+ * `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
933
+ * never asserts against them, so leaving them at storage zero is harmless.
934
+ */
935
+ public async makeTempCheckpointLogOverride(
936
+ checkpointNumber: CheckpointNumber,
937
+ fields: TempCheckpointLogOverrideFields,
938
+ ): Promise<StateOverride> {
939
+ const constants = await this.getRollupConstants();
940
+ const slotAt = (field: TempCheckpointLogField) =>
941
+ `0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
942
+ const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
943
+
944
+ const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
945
+
946
+ if (fields.headerHash) {
947
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
948
+ }
949
+ if (fields.outHash) {
950
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
951
+ }
952
+ if (fields.payloadDigest) {
953
+ stateDiff.push({
954
+ slot: slotAt(TempCheckpointLogField.PayloadDigest),
955
+ value: fields.payloadDigest.toString() as `0x${string}`,
956
+ });
957
+ }
958
+ if (fields.slotNumber !== undefined) {
959
+ // CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
960
+ // so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
961
+ const slotNumber = BigInt(fields.slotNumber);
962
+ if (slotNumber < 0n || slotNumber > 0xffffffffn) {
963
+ throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
964
+ }
965
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
966
+ }
967
+ if (fields.feeHeader) {
968
+ stateDiff.push({
969
+ slot: slotAt(TempCheckpointLogField.FeeHeader),
970
+ value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
971
+ });
972
+ }
973
+
974
+ if (stateDiff.length === 0) {
975
+ return [];
976
+ }
977
+ return [{ address: this.address, stateDiff }];
978
+ }
979
+
980
+ /**
981
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
982
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
983
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
984
+ */
985
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
986
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
987
+ const archiveSlot = hexToBigInt(
988
+ keccak256(
989
+ encodeAbiParameters(
990
+ [{ type: 'uint256' }, { type: 'uint256' }],
991
+ [BigInt(checkpointNumber), archivesMappingBase],
992
+ ),
993
+ ),
994
+ );
995
+ return [
996
+ {
997
+ address: this.address,
998
+ stateDiff: [
999
+ {
1000
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
1001
+ value: archive.toString(),
1002
+ },
1003
+ ],
1004
+ },
1005
+ ];
1006
+ }
1007
+
1008
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
1009
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
1010
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
1011
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
1012
+ for (const override of overrides) {
1013
+ for (const entry of override) {
1014
+ const key = entry.address.toLowerCase();
1015
+ const existing = byAddress.get(key);
1016
+ if (existing) {
1017
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
1018
+ if (entry.balance !== undefined) {
1019
+ existing.balance = entry.balance;
1020
+ }
1021
+ } else {
1022
+ byAddress.set(key, {
1023
+ address: entry.address,
1024
+ balance: entry.balance,
1025
+ stateDiff: [...(entry.stateDiff ?? [])],
1026
+ });
1027
+ }
1028
+ }
1029
+ }
1030
+ return [...byAddress.values()];
1031
+ }
1032
+
1033
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
1034
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
1035
+ const MASK_48_BITS = (1n << 48n) - 1n;
1036
+ const MASK_64_BITS = (1n << 64n) - 1n;
1037
+ const MASK_63_BITS = (1n << 63n) - 1n;
1038
+
1039
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
1040
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
1041
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
1042
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
1043
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
1044
+ value |= 1n << 255n; // preheat flag
1045
+ return value;
1046
+ }
1047
+
1048
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
1049
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
1050
+ public static computeChildFeeHeader(
1051
+ parentFeeHeader: FeeHeader,
1052
+ childManaUsed: bigint,
1053
+ feeAssetPriceModifier: bigint,
1054
+ manaTarget: bigint,
1055
+ ): FeeHeader {
1056
+ const MIN_ETH_PER_FEE_ASSET = 100n;
1057
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
1058
+
1059
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
1060
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
1061
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
1062
+
1063
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
1064
+ const parentPrice =
1065
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
1066
+ let newPrice: bigint;
1067
+ if (feeAssetPriceModifier >= 0n) {
1068
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
1069
+ } else {
1070
+ const absMod = -feeAssetPriceModifier;
1071
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
1072
+ }
1073
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
1074
+ newPrice = MIN_ETH_PER_FEE_ASSET;
1075
+ }
1076
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1077
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1078
+ }
1079
+
1080
+ return {
1081
+ excessMana,
1082
+ manaUsed: childManaUsed,
1083
+ ethPerFeeAsset: newPrice,
1084
+ congestionCost: 0n,
1085
+ proverCost: 0n,
1086
+ };
1087
+ }
1088
+
799
1089
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
800
1090
  public buildInvalidateBadAttestationRequest(
801
1091
  checkpointNumber: CheckpointNumber,
@@ -805,6 +1095,7 @@ export class RollupContract {
805
1095
  ): L1TxRequest {
806
1096
  return {
807
1097
  to: this.address,
1098
+ abi: RollupAbi,
808
1099
  data: encodeFunctionData({
809
1100
  abi: RollupAbi,
810
1101
  functionName: 'invalidateBadAttestation',
@@ -826,6 +1117,7 @@ export class RollupContract {
826
1117
  ): L1TxRequest {
827
1118
  return {
828
1119
  to: this.address,
1120
+ abi: RollupAbi,
829
1121
  data: encodeFunctionData({
830
1122
  abi: RollupAbi,
831
1123
  functionName: 'invalidateInsufficientAttestations',
@@ -842,8 +1134,18 @@ export class RollupContract {
842
1134
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
843
1135
  }
844
1136
 
845
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
846
- return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
1137
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1138
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
1139
+ }
1140
+
1141
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1142
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1143
+ return {
1144
+ sequencerCost: result.sequencerCost,
1145
+ proverCost: result.proverCost,
1146
+ congestionCost: result.congestionCost,
1147
+ congestionMultiplier: result.congestionMultiplier,
1148
+ };
847
1149
  }
848
1150
 
849
1151
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -889,15 +1191,21 @@ export class RollupContract {
889
1191
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
890
1192
  }
891
1193
 
892
- async getAttesters(): Promise<EthAddress[]> {
893
- const attesterSize = await this.getActiveAttesterCount();
1194
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
1195
+ // Pin every read to a single L1 block so the attester count and the chunked index reads
1196
+ // observe a consistent set. Without this, the count and each chunk default to `latest` and
1197
+ // can straddle a block boundary (or reorg), yielding an inconsistent or truncated set.
1198
+ const block = await this.client.getBlock();
1199
+ const blockNumber = block.number ?? undefined;
1200
+ const ts = timestamp ?? block.timestamp;
1201
+ const attesterSize = await this.getActiveAttesterCount({ blockNumber });
894
1202
  const gse = new GSEContract(this.client, await this.getGSE());
895
- const ts = (await this.client.getBlock()).timestamp;
896
-
897
1203
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
898
1204
  const chunks = chunk(indices, 1000);
899
1205
 
900
- const results = await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)));
1206
+ const results = await Promise.all(
1207
+ chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, { blockNumber })),
1208
+ );
901
1209
  return results.flat().map(addr => EthAddress.fromString(addr));
902
1210
  }
903
1211
 
@@ -959,6 +1267,7 @@ export class RollupContract {
959
1267
  setupEpoch(l1TxUtils: L1TxUtils) {
960
1268
  return l1TxUtils.sendAndMonitorTransaction({
961
1269
  to: this.address,
1270
+ abi: RollupAbi,
962
1271
  data: encodeFunctionData({
963
1272
  abi: RollupAbi,
964
1273
  functionName: 'setupEpoch',
@@ -970,6 +1279,7 @@ export class RollupContract {
970
1279
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
971
1280
  return l1TxUtils.sendAndMonitorTransaction({
972
1281
  to: this.address,
1282
+ abi: RollupAbi,
973
1283
  data: encodeFunctionData({
974
1284
  abi: RollupAbi,
975
1285
  functionName: 'vote',
@@ -997,7 +1307,7 @@ export class RollupContract {
997
1307
  }
998
1308
 
999
1309
  public listenToCheckpointInvalidated(
1000
- callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
1310
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
1001
1311
  ): WatchContractEventReturnType {
1002
1312
  return this.rollup.watchEvent.CheckpointInvalidated(
1003
1313
  {},
@@ -1006,7 +1316,7 @@ export class RollupContract {
1006
1316
  for (const log of logs) {
1007
1317
  const args = log.args;
1008
1318
  if (args.checkpointNumber !== undefined) {
1009
- callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
1319
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
1010
1320
  }
1011
1321
  }
1012
1322
  },
@@ -1039,6 +1349,17 @@ export class RollupContract {
1039
1349
  );
1040
1350
  }
1041
1351
 
1352
+ /**
1353
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1354
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1355
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1356
+ * probe to detect RPC nodes that prune historical logs.
1357
+ */
1358
+ async getOwnershipTransferredEventsAtDeploy() {
1359
+ const l1StartBlock = await this.getL1StartBlock();
1360
+ return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
1361
+ }
1362
+
1042
1363
  /** Fetches CheckpointProposed events within the given block range. */
1043
1364
  async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1044
1365
  const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
@@ -1052,9 +1373,67 @@ export class RollupContract {
1052
1373
  checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1053
1374
  archive: Fr.fromString(log.args.archive!),
1054
1375
  versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1055
- attestationsHash: log.args.attestationsHash ? Buffer32.fromString(log.args.attestationsHash) : undefined,
1056
- payloadDigest: log.args.payloadDigest ? Buffer32.fromString(log.args.payloadDigest) : undefined,
1376
+ attestationsHash: (() => {
1377
+ if (!log.args.attestationsHash) {
1378
+ throw new Error(
1379
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1380
+ );
1381
+ }
1382
+ return Buffer32.fromString(log.args.attestationsHash);
1383
+ })(),
1384
+ payloadDigest: (() => {
1385
+ if (!log.args.payloadDigest) {
1386
+ throw new Error(
1387
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1388
+ );
1389
+ }
1390
+ return Buffer32.fromString(log.args.payloadDigest);
1391
+ })(),
1057
1392
  },
1058
1393
  }));
1059
1394
  }
1395
+
1396
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1397
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1398
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1399
+ }
1400
+
1401
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1402
+ static get chainTipsStorageSlot(): bigint {
1403
+ return BigInt(RollupContract.stfStorageSlot);
1404
+ }
1405
+
1406
+ /**
1407
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1408
+ * @param checkpointNumber - The checkpoint number
1409
+ * @param field - The field within the CompressedTempCheckpointLog struct
1410
+ */
1411
+ async getTempCheckpointLogStorageSlot(
1412
+ checkpointNumber: CheckpointNumber,
1413
+ field: TempCheckpointLogField,
1414
+ ): Promise<bigint> {
1415
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1416
+ this.getEpochDuration(),
1417
+ this.getProofSubmissionEpochs(),
1418
+ ]);
1419
+ return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
1420
+ }
1421
+
1422
+ private computeTempCheckpointLogStorageSlot(
1423
+ checkpointNumber: CheckpointNumber,
1424
+ field: TempCheckpointLogField,
1425
+ constants: { epochDuration: number; proofSubmissionEpochs: number },
1426
+ ): bigint {
1427
+ const fieldOffset = BigInt(field);
1428
+ const { epochDuration, proofSubmissionEpochs } = constants;
1429
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1430
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1431
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1432
+ const entryBase = BigInt(
1433
+ keccak256(
1434
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1435
+ ),
1436
+ );
1437
+ return entryBase + fieldOffset;
1438
+ }
1060
1439
  }