@aztec/ethereum 0.0.1-commit.96bb3f7 → 0.0.1-commit.993d240

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 (172) hide show
  1. package/dest/client.d.ts +16 -2
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +27 -7
  4. package/dest/config.d.ts +22 -33
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +59 -64
  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/inbox.d.ts +18 -1
  23. package/dest/contracts/inbox.d.ts.map +1 -1
  24. package/dest/contracts/inbox.js +38 -2
  25. package/dest/contracts/index.d.ts +5 -3
  26. package/dest/contracts/index.d.ts.map +1 -1
  27. package/dest/contracts/index.js +4 -2
  28. package/dest/contracts/log.d.ts +13 -0
  29. package/dest/contracts/log.d.ts.map +1 -0
  30. package/dest/contracts/log.js +1 -0
  31. package/dest/contracts/multicall.d.ts +127 -12
  32. package/dest/contracts/multicall.d.ts.map +1 -1
  33. package/dest/contracts/multicall.js +201 -93
  34. package/dest/contracts/outbox.d.ts +21 -4
  35. package/dest/contracts/outbox.d.ts.map +1 -1
  36. package/dest/contracts/outbox.js +29 -6
  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 +5288 -27
  41. package/dest/contracts/rollup.d.ts.map +1 -1
  42. package/dest/contracts/rollup.js +432 -69
  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 +9 -8
  47. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  48. package/dest/deploy_aztec_l1_contracts.js +58 -32
  49. package/dest/deploy_l1_contract.js +3 -3
  50. package/dest/generated/l1-contracts-defaults.d.ts +30 -0
  51. package/dest/generated/l1-contracts-defaults.d.ts.map +1 -0
  52. package/dest/generated/l1-contracts-defaults.js +30 -0
  53. package/dest/l1_artifacts.d.ts +15699 -16241
  54. package/dest/l1_artifacts.d.ts.map +1 -1
  55. package/dest/l1_artifacts.js +9 -24
  56. package/dest/l1_contract_addresses.d.ts +61 -65
  57. package/dest/l1_contract_addresses.d.ts.map +1 -1
  58. package/dest/l1_contract_addresses.js +101 -82
  59. package/dest/l1_reader.d.ts +5 -5
  60. package/dest/l1_reader.d.ts.map +1 -1
  61. package/dest/l1_reader.js +9 -7
  62. package/dest/l1_tx_utils/config.d.ts +7 -1
  63. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  64. package/dest/l1_tx_utils/config.js +14 -1
  65. package/dest/l1_tx_utils/constants.d.ts +1 -1
  66. package/dest/l1_tx_utils/constants.js +2 -2
  67. package/dest/l1_tx_utils/factory.d.ts +18 -10
  68. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  69. package/dest/l1_tx_utils/factory.js +17 -7
  70. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  71. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  72. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  73. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  75. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  76. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  77. package/dest/l1_tx_utils/index-blobs.js +2 -2
  78. package/dest/l1_tx_utils/index.d.ts +2 -1
  79. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  80. package/dest/l1_tx_utils/index.js +1 -0
  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 +101 -66
  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 +11 -7
  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/publisher_manager.d.ts +21 -6
  91. package/dest/publisher_manager.d.ts.map +1 -1
  92. package/dest/publisher_manager.js +81 -7
  93. package/dest/queries.d.ts +14 -3
  94. package/dest/queries.d.ts.map +1 -1
  95. package/dest/queries.js +66 -4
  96. package/dest/test/chain_monitor.d.ts +23 -3
  97. package/dest/test/chain_monitor.d.ts.map +1 -1
  98. package/dest/test/chain_monitor.js +38 -2
  99. package/dest/test/eth_cheat_codes.d.ts +24 -4
  100. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  101. package/dest/test/eth_cheat_codes.js +17 -4
  102. package/dest/test/index.d.ts +1 -3
  103. package/dest/test/index.d.ts.map +1 -1
  104. package/dest/test/index.js +0 -2
  105. package/dest/test/rollup_cheat_codes.d.ts +34 -4
  106. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  107. package/dest/test/rollup_cheat_codes.js +69 -8
  108. package/dest/test/start_anvil.d.ts +23 -3
  109. package/dest/test/start_anvil.d.ts.map +1 -1
  110. package/dest/test/start_anvil.js +143 -29
  111. package/dest/test/upgrade_utils.js +2 -2
  112. package/dest/utils.d.ts +9 -1
  113. package/dest/utils.d.ts.map +1 -1
  114. package/dest/utils.js +81 -12
  115. package/package.json +9 -10
  116. package/src/client.ts +28 -2
  117. package/src/config.ts +75 -65
  118. package/src/contracts/README.md +157 -0
  119. package/src/contracts/chain_state_override.ts +200 -0
  120. package/src/contracts/fee_asset_handler.ts +2 -0
  121. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  122. package/src/contracts/governance.ts +279 -13
  123. package/src/contracts/governance_proposer.ts +58 -2
  124. package/src/contracts/inbox.ts +57 -2
  125. package/src/contracts/index.ts +4 -2
  126. package/src/contracts/log.ts +13 -0
  127. package/src/contracts/multicall.ts +243 -102
  128. package/src/contracts/outbox.ts +38 -8
  129. package/src/contracts/registry.ts +31 -1
  130. package/src/contracts/rollup.ts +500 -68
  131. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +16 -14
  132. package/src/deploy_aztec_l1_contracts.ts +81 -40
  133. package/src/deploy_l1_contract.ts +3 -3
  134. package/src/generated/l1-contracts-defaults.ts +32 -0
  135. package/src/l1_artifacts.ts +12 -35
  136. package/src/l1_contract_addresses.ts +120 -94
  137. package/src/l1_reader.ts +17 -10
  138. package/src/l1_tx_utils/config.ts +20 -0
  139. package/src/l1_tx_utils/constants.ts +2 -2
  140. package/src/l1_tx_utils/factory.ts +31 -31
  141. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  142. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  143. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  144. package/src/l1_tx_utils/index-blobs.ts +2 -2
  145. package/src/l1_tx_utils/index.ts +1 -0
  146. package/src/l1_tx_utils/l1_tx_utils.ts +99 -48
  147. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +11 -7
  148. package/src/{test → l1_tx_utils}/tx_delayer.ts +93 -56
  149. package/src/publisher_manager.ts +107 -10
  150. package/src/queries.ts +73 -5
  151. package/src/test/chain_monitor.ts +65 -3
  152. package/src/test/eth_cheat_codes.ts +16 -4
  153. package/src/test/index.ts +0 -2
  154. package/src/test/rollup_cheat_codes.ts +75 -8
  155. package/src/test/start_anvil.ts +177 -29
  156. package/src/test/upgrade_utils.ts +2 -2
  157. package/src/utils.ts +88 -14
  158. package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
  159. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  160. package/dest/contracts/empire_slashing_proposer.js +0 -209
  161. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  162. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  163. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  164. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  165. package/dest/test/delayed_tx_utils.d.ts +0 -13
  166. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  167. package/dest/test/delayed_tx_utils.js +0 -28
  168. package/dest/test/tx_delayer.d.ts +0 -36
  169. package/dest/test/tx_delayer.d.ts.map +0 -1
  170. package/src/contracts/empire_slashing_proposer.ts +0 -270
  171. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  172. 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 { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
8
10
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
9
11
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
10
12
 
@@ -13,8 +15,10 @@ import {
13
15
  type Account,
14
16
  type GetContractReturnType,
15
17
  type Hex,
18
+ type Log,
16
19
  type StateOverride,
17
20
  type WatchContractEventReturnType,
21
+ encodeAbiParameters,
18
22
  encodeFunctionData,
19
23
  getContract,
20
24
  hexToBigInt,
@@ -28,10 +32,10 @@ import type { L1ReaderConfig } from '../l1_reader.js';
28
32
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
29
33
  import type { ViemClient } from '../types.js';
30
34
  import { formatViemError } from '../utils.js';
31
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
32
35
  import { GSEContract } from './gse.js';
36
+ import type { L1EventLog } from './log.js';
33
37
  import { SlasherContract } from './slasher_contract.js';
34
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
38
+ import { SlashingProposerContract } from './slashing_proposer.js';
35
39
  import { checkBlockTag } from './utils.js';
36
40
 
37
41
  export type ViemCommitteeAttestation = {
@@ -53,7 +57,6 @@ export type L1RollupContractAddresses = Pick<
53
57
  | 'feeJuiceAddress'
54
58
  | 'stakingAssetAddress'
55
59
  | 'rewardDistributorAddress'
56
- | 'slashFactoryAddress'
57
60
  | 'gseAddress'
58
61
  >;
59
62
 
@@ -69,6 +72,7 @@ export type ViemHeader = {
69
72
  blockHeadersHash: `0x${string}`;
70
73
  blobsHash: `0x${string}`;
71
74
  inHash: `0x${string}`;
75
+ outHash: `0x${string}`;
72
76
  slotNumber: bigint;
73
77
  timestamp: bigint;
74
78
  coinbase: `0x${string}`;
@@ -82,12 +86,6 @@ export type ViemGasFees = {
82
86
  feePerL2Gas: bigint;
83
87
  };
84
88
 
85
- export enum SlashingProposerType {
86
- None = 0,
87
- Tally = 1,
88
- Empire = 2,
89
- }
90
-
91
89
  /**
92
90
  * Status of a validator/attester in the staking system.
93
91
  * Matches the Status enum in StakingLib.sol
@@ -105,7 +103,7 @@ export enum AttesterStatus {
105
103
  export type FeeHeader = {
106
104
  excessMana: bigint;
107
105
  manaUsed: bigint;
108
- feeAssetPriceNumerator: bigint;
106
+ ethPerFeeAsset: bigint;
109
107
  congestionCost: bigint;
110
108
  proverCost: bigint;
111
109
  };
@@ -131,6 +129,39 @@ export type L1FeeData = {
131
129
  blobFee: bigint;
132
130
  };
133
131
 
132
+ /** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
133
+ export enum TempCheckpointLogField {
134
+ HeaderHash = 0,
135
+ BlobCommitmentsHash = 1,
136
+ OutHash = 2,
137
+ AttestationsHash = 3,
138
+ PayloadDigest = 4,
139
+ SlotNumber = 5,
140
+ FeeHeader = 6,
141
+ }
142
+
143
+ /**
144
+ * Field-level override input for `tempCheckpointLogs[checkpointNumber]`. Covers the fields the
145
+ * `propose()` path actually reads back. `payloadDigest` is `Buffer32` because it carries an
146
+ * arbitrary `bytes32` value rather than a BN254 scalar. `slotNumber` carries the uint32 portion
147
+ * of the on-chain `CompressedSlot`.
148
+ */
149
+ export type TempCheckpointLogOverrideFields = {
150
+ headerHash?: Fr;
151
+ outHash?: Fr;
152
+ payloadDigest?: Buffer32;
153
+ slotNumber?: SlotNumber;
154
+ feeHeader?: FeeHeader;
155
+ };
156
+
157
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
158
+ export type ManaMinFeeComponents = {
159
+ sequencerCost: bigint;
160
+ proverCost: bigint;
161
+ congestionCost: bigint;
162
+ congestionMultiplier: bigint;
163
+ };
164
+
134
165
  /**
135
166
  * Reward configuration for the rollup
136
167
  */
@@ -185,10 +216,29 @@ export type RollupStatusResponse = {
185
216
  archiveOfMyCheckpoint: Fr;
186
217
  };
187
218
 
219
+ /** Arguments for the CheckpointProposed event. */
220
+ export type CheckpointProposedArgs = {
221
+ checkpointNumber: CheckpointNumber;
222
+ archive: Fr;
223
+ versionedBlobHashes: Buffer[];
224
+ /** Hash of attestations emitted in the CheckpointProposed event. */
225
+ attestationsHash: Buffer32;
226
+ /** Digest of the payload emitted in the CheckpointProposed event. */
227
+ payloadDigest: Buffer32;
228
+ };
229
+
230
+ /** Log type for CheckpointProposed events. */
231
+ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
232
+
188
233
  export class RollupContract {
189
234
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
235
+ private readonly logger = createLogger('ethereum:rollup');
190
236
 
191
237
  private static cachedStfStorageSlot: Hex | undefined;
238
+ private cachedEscapeHatch?: {
239
+ address: EthAddress;
240
+ contract: GetContractReturnType<typeof EscapeHatchAbi, ViemClient>;
241
+ };
192
242
 
193
243
  static get checkBlobStorageSlot(): bigint {
194
244
  const asString = RollupStorage.find(storage => storage.label === 'checkBlob')?.slot;
@@ -212,7 +262,7 @@ export class RollupContract {
212
262
 
213
263
  static getFromConfig(config: L1ReaderConfig) {
214
264
  const client = getPublicClient(config);
215
- const address = config.l1Contracts.rollupAddress.toString();
265
+ const address = config.rollupAddress.toString();
216
266
  return new RollupContract(client, address);
217
267
  }
218
268
 
@@ -238,34 +288,18 @@ export class RollupContract {
238
288
  return this.rollup;
239
289
  }
240
290
 
241
- public async getSlashingProposer(): Promise<
242
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
243
- > {
291
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
244
292
  const slasher = await this.getSlasherContract();
245
293
  if (!slasher) {
246
294
  return undefined;
247
295
  }
248
296
 
249
297
  const proposerAddress = await slasher.getProposer();
250
- const proposerAbi = [
251
- {
252
- type: 'function',
253
- name: 'SLASHING_PROPOSER_TYPE',
254
- inputs: [],
255
- outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
256
- stateMutability: 'view',
257
- },
258
- ] as const;
259
-
260
- const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
261
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
262
- if (proposerType === SlashingProposerType.Tally.valueOf()) {
263
- return new TallySlashingProposerContract(this.client, proposerAddress);
264
- } else if (proposerType === SlashingProposerType.Empire.valueOf()) {
265
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
266
- } else {
267
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
298
+ if (proposerAddress.isZero()) {
299
+ return undefined;
268
300
  }
301
+
302
+ return new SlashingProposerContract(this.client, proposerAddress);
269
303
  }
270
304
 
271
305
  @memoize
@@ -358,6 +392,20 @@ export class RollupContract {
358
392
  return Fr.fromString(await this.rollup.read.archiveAt([0n]));
359
393
  }
360
394
 
395
+ @memoize
396
+ async getVkTreeRoot(): Promise<Fr> {
397
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
398
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
399
+ return Fr.fromString(value ?? '0x0');
400
+ }
401
+
402
+ @memoize
403
+ async getProtocolContractsHash(): Promise<Fr> {
404
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
405
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
406
+ return Fr.fromString(value ?? '0x0');
407
+ }
408
+
361
409
  /**
362
410
  * Returns rollup constants used for epoch queries.
363
411
  * Return type is `L1RollupConstants` which is defined in stdlib,
@@ -370,13 +418,25 @@ export class RollupContract {
370
418
  slotDuration: number;
371
419
  epochDuration: number;
372
420
  proofSubmissionEpochs: number;
421
+ targetCommitteeSize: number;
422
+ rollupManaLimit: number;
373
423
  }> {
374
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
424
+ const [
425
+ l1StartBlock,
426
+ l1GenesisTime,
427
+ slotDuration,
428
+ epochDuration,
429
+ proofSubmissionEpochs,
430
+ targetCommitteeSize,
431
+ rollupManaLimit,
432
+ ] = await Promise.all([
375
433
  this.getL1StartBlock(),
376
434
  this.getL1GenesisTime(),
377
435
  this.getSlotDuration(),
378
436
  this.getEpochDuration(),
379
437
  this.getProofSubmissionEpochs(),
438
+ this.getTargetCommitteeSize(),
439
+ this.getManaLimit(),
380
440
  ]);
381
441
  return {
382
442
  l1StartBlock,
@@ -384,6 +444,8 @@ export class RollupContract {
384
444
  slotDuration,
385
445
  epochDuration: Number(epochDuration),
386
446
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
447
+ targetCommitteeSize,
448
+ rollupManaLimit: Number(rollupManaLimit),
387
449
  };
388
450
  }
389
451
 
@@ -391,6 +453,62 @@ export class RollupContract {
391
453
  return EthAddress.fromString(await this.rollup.read.getSlasher());
392
454
  }
393
455
 
456
+ /**
457
+ * Returns the configured escape hatch contract address, or zero if disabled.
458
+ */
459
+ async getEscapeHatchAddress(): Promise<EthAddress> {
460
+ return EthAddress.fromString(await this.rollup.read.getEscapeHatch());
461
+ }
462
+
463
+ private async getEscapeHatchContract(): Promise<
464
+ GetContractReturnType<typeof EscapeHatchAbi, ViemClient> | undefined
465
+ > {
466
+ const escapeHatchAddress = await this.getEscapeHatchAddress();
467
+ if (escapeHatchAddress.isZero()) {
468
+ return undefined;
469
+ }
470
+
471
+ // Cache the viem contract wrapper since it will be used frequently.
472
+ if (!this.cachedEscapeHatch || !this.cachedEscapeHatch.address.equals(escapeHatchAddress)) {
473
+ this.cachedEscapeHatch = {
474
+ address: escapeHatchAddress,
475
+ contract: getContract({
476
+ address: escapeHatchAddress.toString(),
477
+ abi: EscapeHatchAbi,
478
+ client: this.client,
479
+ }),
480
+ };
481
+ }
482
+
483
+ return this.cachedEscapeHatch.contract;
484
+ }
485
+
486
+ /**
487
+ * Returns whether the escape hatch is open for the given epoch.
488
+ * If escape hatch is not configured, returns false.
489
+ *
490
+ * This function is intentionally defensive: any failure to query the escape hatch
491
+ * (RPC issues, transient errors, etc.) is treated as "closed" to avoid callers
492
+ * needing to sprinkle try/catch everywhere.
493
+ */
494
+ async isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean> {
495
+ try {
496
+ const escapeHatch = await this.getEscapeHatchContract();
497
+ if (!escapeHatch) {
498
+ return false;
499
+ }
500
+
501
+ const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
502
+ return isOpen;
503
+ } catch (err) {
504
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
505
+ epoch: Number(epoch),
506
+ error: err,
507
+ });
508
+ return false;
509
+ }
510
+ }
511
+
394
512
  /**
395
513
  * Returns a SlasherContract instance for interacting with the slasher contract.
396
514
  */
@@ -422,28 +540,43 @@ export class RollupContract {
422
540
  return this.rollup.read.getCheckpointReward();
423
541
  }
424
542
 
425
- async getCheckpointNumber(): Promise<CheckpointNumber> {
426
- return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
543
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
544
+ await checkBlockTag(options?.blockNumber, this.client);
545
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
427
546
  }
428
547
 
429
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
430
- return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber());
548
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
549
+ await checkBlockTag(options?.blockNumber, this.client);
550
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
431
551
  }
432
552
 
433
- async getSlotNumber(): Promise<SlotNumber> {
434
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
553
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
554
+ await checkBlockTag(options?.blockNumber, this.client);
555
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
435
556
  }
436
557
 
437
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
438
- const result = await this.rollup.read.getL1FeesAt([timestamp]);
558
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
559
+ await checkBlockTag(options?.blockNumber, this.client);
560
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
439
561
  return {
440
562
  baseFee: result.baseFee,
441
563
  blobFee: result.blobFee,
442
564
  };
443
565
  }
444
566
 
445
- getFeeAssetPerEth(): Promise<bigint> {
446
- return this.rollup.read.getFeeAssetPerEth();
567
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
568
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
569
+ return {
570
+ excessMana: result.excessMana,
571
+ manaUsed: result.manaUsed,
572
+ ethPerFeeAsset: result.ethPerFeeAsset,
573
+ congestionCost: result.congestionCost,
574
+ proverCost: result.proverCost,
575
+ };
576
+ }
577
+
578
+ getEthPerFeeAsset(): Promise<bigint> {
579
+ return this.rollup.read.getEthPerFeeAsset();
447
580
  }
448
581
 
449
582
  async getCommitteeAt(timestamp: bigint): Promise<EthAddress[] | undefined> {
@@ -516,8 +649,9 @@ export class RollupContract {
516
649
  return EthAddress.fromString(result);
517
650
  }
518
651
 
519
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
520
- const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)]);
652
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
653
+ await checkBlockTag(options?.blockNumber, this.client);
654
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
521
655
  return {
522
656
  archive: Fr.fromString(result.archive),
523
657
  headerHash: Buffer32.fromString(result.headerHash),
@@ -528,7 +662,7 @@ export class RollupContract {
528
662
  feeHeader: {
529
663
  excessMana: result.feeHeader.excessMana,
530
664
  manaUsed: result.feeHeader.manaUsed,
531
- feeAssetPriceNumerator: result.feeHeader.feeAssetPriceNumerator,
665
+ ethPerFeeAsset: result.feeHeader.ethPerFeeAsset,
532
666
  congestionCost: result.feeHeader.congestionCost,
533
667
  proverCost: result.feeHeader.proverCost,
534
668
  },
@@ -550,6 +684,30 @@ export class RollupContract {
550
684
  );
551
685
  }
552
686
 
687
+ /**
688
+ * Returns the effective pending checkpoint, accounting for potential prunes.
689
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
690
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
691
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
692
+ * @param options - Optional L1 block number to pin the queries to.
693
+ */
694
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
695
+ return retry(
696
+ async () => {
697
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
698
+ const canPrune = await this.canPruneAtTime(timestamp, options);
699
+ if (canPrune) {
700
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
701
+ return await this.getCheckpoint(provenCheckpointNumber, options);
702
+ }
703
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
704
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
705
+ },
706
+ 'getting effective pending checkpoint',
707
+ makeBackoff([0.5, 0.5, 0.5]),
708
+ );
709
+ }
710
+
553
711
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
554
712
  const { pending, proven } = await this.rollup.read.getTips();
555
713
  return {
@@ -668,14 +826,13 @@ export class RollupContract {
668
826
  * timestamp of the next L1 block
669
827
  * @throws otherwise
670
828
  */
671
- public async canProposeAtNextEthBlock(
829
+ public async canProposeAt(
672
830
  archive: Buffer,
673
831
  account: `0x${string}` | Account,
674
- slotDuration: number,
675
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
832
+ timestamp: bigint,
833
+ stateOverride: StateOverride = [],
676
834
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
677
- const latestBlock = await this.client.getBlock();
678
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
835
+ const timeOfNextL1Slot = timestamp;
679
836
  const who = typeof account === 'string' ? account : account.address;
680
837
 
681
838
  try {
@@ -687,7 +844,7 @@ export class RollupContract {
687
844
  functionName: 'canProposeAtTime',
688
845
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
689
846
  account,
690
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
847
+ stateOverride,
691
848
  });
692
849
 
693
850
  return {
@@ -701,20 +858,32 @@ export class RollupContract {
701
858
  }
702
859
 
703
860
  /**
704
- * Returns a state override that sets the pending checkpoint number to the specified value. Useful for simulations.
705
- * Requires querying the current state of the contract to get the current proven checkpoint number, as they are both
706
- * stored in the same slot. If the argument is undefined, it returns an empty override.
861
+ * Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
862
+ * Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
863
+ * a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
864
+ * value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
865
+ *
866
+ * Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
867
+ * `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
707
868
  */
708
- public async makePendingCheckpointNumberOverride(
709
- forcePendingCheckpointNumber: CheckpointNumber | undefined,
710
- ): Promise<StateOverride> {
711
- if (forcePendingCheckpointNumber === undefined) {
869
+ public async makeChainTipsOverride(override: {
870
+ pending?: CheckpointNumber;
871
+ proven?: CheckpointNumber;
872
+ }): Promise<StateOverride> {
873
+ if (override.pending === undefined && override.proven === undefined) {
712
874
  return [];
713
875
  }
714
876
  const slot = RollupContract.stfStorageSlot;
715
877
  const currentValue = await this.client.getStorageAt({ address: this.address, slot });
716
- const currentProvenCheckpointNumber = currentValue ? hexToBigInt(currentValue) & ((1n << 128n) - 1n) : 0n;
717
- const newValue = (BigInt(forcePendingCheckpointNumber) << 128n) | currentProvenCheckpointNumber;
878
+ const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
879
+ const currentPending = currentRaw >> 128n;
880
+ const currentProven = currentRaw & ((1n << 128n) - 1n);
881
+ const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
882
+ const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
883
+ if (newProven > newPending) {
884
+ throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
885
+ }
886
+ const newValue = (newPending << 128n) | newProven;
718
887
  return [
719
888
  {
720
889
  address: this.address,
@@ -723,6 +892,168 @@ export class RollupContract {
723
892
  ];
724
893
  }
725
894
 
895
+ /**
896
+ * Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
897
+ * the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
898
+ * pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
899
+ *
900
+ * `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
901
+ * never asserts against them, so leaving them at storage zero is harmless.
902
+ */
903
+ public async makeTempCheckpointLogOverride(
904
+ checkpointNumber: CheckpointNumber,
905
+ fields: TempCheckpointLogOverrideFields,
906
+ ): Promise<StateOverride> {
907
+ const constants = await this.getRollupConstants();
908
+ const slotAt = (field: TempCheckpointLogField) =>
909
+ `0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
910
+ const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
911
+
912
+ const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
913
+
914
+ if (fields.headerHash) {
915
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
916
+ }
917
+ if (fields.outHash) {
918
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
919
+ }
920
+ if (fields.payloadDigest) {
921
+ stateDiff.push({
922
+ slot: slotAt(TempCheckpointLogField.PayloadDigest),
923
+ value: fields.payloadDigest.toString() as `0x${string}`,
924
+ });
925
+ }
926
+ if (fields.slotNumber !== undefined) {
927
+ // CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
928
+ // so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
929
+ const slotNumber = BigInt(fields.slotNumber);
930
+ if (slotNumber < 0n || slotNumber > 0xffffffffn) {
931
+ throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
932
+ }
933
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
934
+ }
935
+ if (fields.feeHeader) {
936
+ stateDiff.push({
937
+ slot: slotAt(TempCheckpointLogField.FeeHeader),
938
+ value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
939
+ });
940
+ }
941
+
942
+ if (stateDiff.length === 0) {
943
+ return [];
944
+ }
945
+ return [{ address: this.address, stateDiff }];
946
+ }
947
+
948
+ /**
949
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
950
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
951
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
952
+ */
953
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
954
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
955
+ const archiveSlot = hexToBigInt(
956
+ keccak256(
957
+ encodeAbiParameters(
958
+ [{ type: 'uint256' }, { type: 'uint256' }],
959
+ [BigInt(checkpointNumber), archivesMappingBase],
960
+ ),
961
+ ),
962
+ );
963
+ return [
964
+ {
965
+ address: this.address,
966
+ stateDiff: [
967
+ {
968
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
969
+ value: archive.toString(),
970
+ },
971
+ ],
972
+ },
973
+ ];
974
+ }
975
+
976
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
977
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
978
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
979
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
980
+ for (const override of overrides) {
981
+ for (const entry of override) {
982
+ const key = entry.address.toLowerCase();
983
+ const existing = byAddress.get(key);
984
+ if (existing) {
985
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
986
+ if (entry.balance !== undefined) {
987
+ existing.balance = entry.balance;
988
+ }
989
+ } else {
990
+ byAddress.set(key, {
991
+ address: entry.address,
992
+ balance: entry.balance,
993
+ stateDiff: [...(entry.stateDiff ?? [])],
994
+ });
995
+ }
996
+ }
997
+ }
998
+ return [...byAddress.values()];
999
+ }
1000
+
1001
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
1002
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
1003
+ const MASK_48_BITS = (1n << 48n) - 1n;
1004
+ const MASK_64_BITS = (1n << 64n) - 1n;
1005
+ const MASK_63_BITS = (1n << 63n) - 1n;
1006
+
1007
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
1008
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
1009
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
1010
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
1011
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
1012
+ value |= 1n << 255n; // preheat flag
1013
+ return value;
1014
+ }
1015
+
1016
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
1017
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
1018
+ public static computeChildFeeHeader(
1019
+ parentFeeHeader: FeeHeader,
1020
+ childManaUsed: bigint,
1021
+ feeAssetPriceModifier: bigint,
1022
+ manaTarget: bigint,
1023
+ ): FeeHeader {
1024
+ const MIN_ETH_PER_FEE_ASSET = 100n;
1025
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
1026
+
1027
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
1028
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
1029
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
1030
+
1031
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
1032
+ const parentPrice =
1033
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
1034
+ let newPrice: bigint;
1035
+ if (feeAssetPriceModifier >= 0n) {
1036
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
1037
+ } else {
1038
+ const absMod = -feeAssetPriceModifier;
1039
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
1040
+ }
1041
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
1042
+ newPrice = MIN_ETH_PER_FEE_ASSET;
1043
+ }
1044
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1045
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1046
+ }
1047
+
1048
+ return {
1049
+ excessMana,
1050
+ manaUsed: childManaUsed,
1051
+ ethPerFeeAsset: newPrice,
1052
+ congestionCost: 0n,
1053
+ proverCost: 0n,
1054
+ };
1055
+ }
1056
+
726
1057
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
727
1058
  public buildInvalidateBadAttestationRequest(
728
1059
  checkpointNumber: CheckpointNumber,
@@ -732,6 +1063,7 @@ export class RollupContract {
732
1063
  ): L1TxRequest {
733
1064
  return {
734
1065
  to: this.address,
1066
+ abi: RollupAbi,
735
1067
  data: encodeFunctionData({
736
1068
  abi: RollupAbi,
737
1069
  functionName: 'invalidateBadAttestation',
@@ -753,6 +1085,7 @@ export class RollupContract {
753
1085
  ): L1TxRequest {
754
1086
  return {
755
1087
  to: this.address,
1088
+ abi: RollupAbi,
756
1089
  data: encodeFunctionData({
757
1090
  abi: RollupAbi,
758
1091
  functionName: 'invalidateInsufficientAttestations',
@@ -769,8 +1102,18 @@ export class RollupContract {
769
1102
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
770
1103
  }
771
1104
 
772
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
773
- return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
1105
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1106
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
1107
+ }
1108
+
1109
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1110
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1111
+ return {
1112
+ sequencerCost: result.sequencerCost,
1113
+ proverCost: result.proverCost,
1114
+ congestionCost: result.congestionCost,
1115
+ congestionMultiplier: result.congestionMultiplier,
1116
+ };
774
1117
  }
775
1118
 
776
1119
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -816,11 +1159,10 @@ export class RollupContract {
816
1159
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
817
1160
  }
818
1161
 
819
- async getAttesters(): Promise<EthAddress[]> {
1162
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
820
1163
  const attesterSize = await this.getActiveAttesterCount();
821
1164
  const gse = new GSEContract(this.client, await this.getGSE());
822
- const ts = (await this.client.getBlock()).timestamp;
823
-
1165
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
824
1166
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
825
1167
  const chunks = chunk(indices, 1000);
826
1168
 
@@ -886,6 +1228,7 @@ export class RollupContract {
886
1228
  setupEpoch(l1TxUtils: L1TxUtils) {
887
1229
  return l1TxUtils.sendAndMonitorTransaction({
888
1230
  to: this.address,
1231
+ abi: RollupAbi,
889
1232
  data: encodeFunctionData({
890
1233
  abi: RollupAbi,
891
1234
  functionName: 'setupEpoch',
@@ -897,6 +1240,7 @@ export class RollupContract {
897
1240
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
898
1241
  return l1TxUtils.sendAndMonitorTransaction({
899
1242
  to: this.address,
1243
+ abi: RollupAbi,
900
1244
  data: encodeFunctionData({
901
1245
  abi: RollupAbi,
902
1246
  functionName: 'vote',
@@ -924,7 +1268,7 @@ export class RollupContract {
924
1268
  }
925
1269
 
926
1270
  public listenToCheckpointInvalidated(
927
- callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
1271
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
928
1272
  ): WatchContractEventReturnType {
929
1273
  return this.rollup.watchEvent.CheckpointInvalidated(
930
1274
  {},
@@ -933,7 +1277,7 @@ export class RollupContract {
933
1277
  for (const log of logs) {
934
1278
  const args = log.args;
935
1279
  if (args.checkpointNumber !== undefined) {
936
- callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
1280
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
937
1281
  }
938
1282
  }
939
1283
  },
@@ -965,4 +1309,92 @@ export class RollupContract {
965
1309
  },
966
1310
  );
967
1311
  }
1312
+
1313
+ /**
1314
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1315
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1316
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1317
+ * probe to detect RPC nodes that prune historical logs.
1318
+ */
1319
+ async getOwnershipTransferredEventsAtDeploy() {
1320
+ const l1StartBlock = await this.getL1StartBlock();
1321
+ return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
1322
+ }
1323
+
1324
+ /** Fetches CheckpointProposed events within the given block range. */
1325
+ async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1326
+ const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
1327
+ return logs
1328
+ .filter(log => log.blockNumber! >= fromBlock && log.blockNumber! <= toBlock)
1329
+ .map(log => ({
1330
+ l1BlockNumber: log.blockNumber!,
1331
+ l1BlockHash: Buffer32.fromString(log.blockHash!),
1332
+ l1TransactionHash: log.transactionHash!,
1333
+ args: {
1334
+ checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1335
+ archive: Fr.fromString(log.args.archive!),
1336
+ versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1337
+ attestationsHash: (() => {
1338
+ if (!log.args.attestationsHash) {
1339
+ throw new Error(
1340
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1341
+ );
1342
+ }
1343
+ return Buffer32.fromString(log.args.attestationsHash);
1344
+ })(),
1345
+ payloadDigest: (() => {
1346
+ if (!log.args.payloadDigest) {
1347
+ throw new Error(
1348
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1349
+ );
1350
+ }
1351
+ return Buffer32.fromString(log.args.payloadDigest);
1352
+ })(),
1353
+ },
1354
+ }));
1355
+ }
1356
+
1357
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1358
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1359
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1360
+ }
1361
+
1362
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1363
+ static get chainTipsStorageSlot(): bigint {
1364
+ return BigInt(RollupContract.stfStorageSlot);
1365
+ }
1366
+
1367
+ /**
1368
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1369
+ * @param checkpointNumber - The checkpoint number
1370
+ * @param field - The field within the CompressedTempCheckpointLog struct
1371
+ */
1372
+ async getTempCheckpointLogStorageSlot(
1373
+ checkpointNumber: CheckpointNumber,
1374
+ field: TempCheckpointLogField,
1375
+ ): Promise<bigint> {
1376
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1377
+ this.getEpochDuration(),
1378
+ this.getProofSubmissionEpochs(),
1379
+ ]);
1380
+ return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
1381
+ }
1382
+
1383
+ private computeTempCheckpointLogStorageSlot(
1384
+ checkpointNumber: CheckpointNumber,
1385
+ field: TempCheckpointLogField,
1386
+ constants: { epochDuration: number; proofSubmissionEpochs: number },
1387
+ ): bigint {
1388
+ const fieldOffset = BigInt(field);
1389
+ const { epochDuration, proofSubmissionEpochs } = constants;
1390
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1391
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1392
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1393
+ const entryBase = BigInt(
1394
+ keccak256(
1395
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1396
+ ),
1397
+ );
1398
+ return entryBase + fieldOffset;
1399
+ }
968
1400
  }