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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) 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 +27 -8
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +60 -13
  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/empire_base.d.ts +1 -3
  11. package/dest/contracts/empire_base.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_price_oracle.d.ts +6 -2
  13. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -1
  14. package/dest/contracts/fee_asset_price_oracle.js +11 -6
  15. package/dest/contracts/governance.d.ts +135 -35
  16. package/dest/contracts/governance.d.ts.map +1 -1
  17. package/dest/contracts/governance.js +226 -15
  18. package/dest/contracts/governance_proposer.d.ts +33 -4
  19. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  20. package/dest/contracts/governance_proposer.js +52 -13
  21. package/dest/contracts/gse.d.ts +8 -2
  22. package/dest/contracts/gse.d.ts.map +1 -1
  23. package/dest/contracts/gse.js +11 -2
  24. package/dest/contracts/inbox.d.ts +3 -3
  25. package/dest/contracts/inbox.d.ts.map +1 -1
  26. package/dest/contracts/inbox.js +12 -7
  27. package/dest/contracts/index.d.ts +3 -3
  28. package/dest/contracts/index.d.ts.map +1 -1
  29. package/dest/contracts/index.js +2 -2
  30. package/dest/contracts/multicall.d.ts +130 -13
  31. package/dest/contracts/multicall.d.ts.map +1 -1
  32. package/dest/contracts/multicall.js +203 -94
  33. package/dest/contracts/outbox.d.ts +27 -4
  34. package/dest/contracts/outbox.d.ts.map +1 -1
  35. package/dest/contracts/outbox.js +34 -7
  36. package/dest/contracts/rollup.d.ts +5423 -27
  37. package/dest/contracts/rollup.d.ts.map +1 -1
  38. package/dest/contracts/rollup.js +396 -73
  39. package/dest/contracts/slashing_proposer.d.ts +170 -0
  40. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  41. package/dest/contracts/slashing_proposer.js +748 -0
  42. package/dest/deploy_aztec_l1_contracts.d.ts +18 -10
  43. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  44. package/dest/deploy_aztec_l1_contracts.js +28 -17
  45. package/dest/foundry_binary.d.ts +13 -0
  46. package/dest/foundry_binary.d.ts.map +1 -0
  47. package/dest/foundry_binary.js +52 -0
  48. package/dest/l1_artifacts.d.ts +14943 -16270
  49. package/dest/l1_artifacts.d.ts.map +1 -1
  50. package/dest/l1_artifacts.js +9 -24
  51. package/dest/l1_contract_addresses.d.ts +61 -65
  52. package/dest/l1_contract_addresses.d.ts.map +1 -1
  53. package/dest/l1_contract_addresses.js +101 -82
  54. package/dest/l1_reader.d.ts +5 -5
  55. package/dest/l1_reader.d.ts.map +1 -1
  56. package/dest/l1_reader.js +9 -7
  57. package/dest/l1_tx_utils/config.js +3 -3
  58. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +23 -4
  59. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -1
  60. package/dest/l1_tx_utils/l1_fee_analyzer.js +65 -3
  61. package/dest/l1_tx_utils/l1_tx_utils.d.ts +9 -1
  62. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  63. package/dest/l1_tx_utils/l1_tx_utils.js +138 -57
  64. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  65. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  66. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +10 -5
  67. package/dest/l1_tx_utils/tx_delayer.d.ts +5 -3
  68. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -1
  69. package/dest/l1_tx_utils/tx_delayer.js +7 -5
  70. package/dest/l1_tx_utils/types.d.ts +27 -1
  71. package/dest/l1_tx_utils/types.d.ts.map +1 -1
  72. package/dest/l1_tx_utils/types.js +10 -0
  73. package/dest/publisher_manager.d.ts +34 -8
  74. package/dest/publisher_manager.d.ts.map +1 -1
  75. package/dest/publisher_manager.js +119 -8
  76. package/dest/queries.d.ts +13 -2
  77. package/dest/queries.d.ts.map +1 -1
  78. package/dest/queries.js +69 -4
  79. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  80. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  81. package/dest/test/blob_kzg_warmup.js +64 -0
  82. package/dest/test/chain_monitor.d.ts +55 -4
  83. package/dest/test/chain_monitor.d.ts.map +1 -1
  84. package/dest/test/chain_monitor.js +104 -3
  85. package/dest/test/eth_cheat_codes.d.ts +37 -6
  86. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  87. package/dest/test/eth_cheat_codes.js +89 -41
  88. package/dest/test/index.d.ts +2 -1
  89. package/dest/test/index.d.ts.map +1 -1
  90. package/dest/test/index.js +1 -0
  91. package/dest/test/rollup_cheat_codes.d.ts +60 -3
  92. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  93. package/dest/test/rollup_cheat_codes.js +86 -7
  94. package/dest/test/start_anvil.d.ts +23 -3
  95. package/dest/test/start_anvil.d.ts.map +1 -1
  96. package/dest/test/start_anvil.js +182 -32
  97. package/dest/utils.d.ts +9 -2
  98. package/dest/utils.d.ts.map +1 -1
  99. package/dest/utils.js +48 -16
  100. package/package.json +6 -9
  101. package/src/client.ts +66 -2
  102. package/src/config.ts +83 -17
  103. package/src/contracts/chain_state_override.ts +200 -0
  104. package/src/contracts/empire_base.ts +0 -2
  105. package/src/contracts/fee_asset_price_oracle.ts +10 -5
  106. package/src/contracts/governance.ts +310 -10
  107. package/src/contracts/governance_proposer.ts +56 -13
  108. package/src/contracts/gse.ts +19 -2
  109. package/src/contracts/inbox.ts +13 -5
  110. package/src/contracts/index.ts +2 -2
  111. package/src/contracts/multicall.ts +247 -106
  112. package/src/contracts/outbox.ts +42 -8
  113. package/src/contracts/rollup.ts +447 -80
  114. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +91 -30
  115. package/src/deploy_aztec_l1_contracts.ts +32 -18
  116. package/src/foundry_binary.ts +57 -0
  117. package/src/l1_artifacts.ts +12 -35
  118. package/src/l1_contract_addresses.ts +120 -94
  119. package/src/l1_reader.ts +17 -10
  120. package/src/l1_tx_utils/config.ts +3 -3
  121. package/src/l1_tx_utils/l1_fee_analyzer.ts +75 -3
  122. package/src/l1_tx_utils/l1_tx_utils.ts +122 -48
  123. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +10 -5
  124. package/src/l1_tx_utils/tx_delayer.ts +16 -7
  125. package/src/l1_tx_utils/types.ts +32 -0
  126. package/src/publisher_manager.ts +143 -12
  127. package/src/queries.ts +76 -4
  128. package/src/test/blob_kzg_warmup.ts +65 -0
  129. package/src/test/chain_monitor.ts +148 -3
  130. package/src/test/eth_cheat_codes.ts +82 -45
  131. package/src/test/index.ts +1 -0
  132. package/src/test/rollup_cheat_codes.ts +115 -6
  133. package/src/test/start_anvil.ts +212 -32
  134. package/src/utils.ts +45 -18
  135. package/dest/contracts/empire_slashing_proposer.d.ts +0 -69
  136. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  137. package/dest/contracts/empire_slashing_proposer.js +0 -216
  138. package/dest/contracts/tally_slashing_proposer.d.ts +0 -140
  139. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  140. package/dest/contracts/tally_slashing_proposer.js +0 -320
  141. package/dest/generated/l1-contracts-defaults.d.ts +0 -30
  142. package/dest/generated/l1-contracts-defaults.d.ts.map +0 -1
  143. package/dest/generated/l1-contracts-defaults.js +0 -30
  144. package/src/contracts/empire_slashing_proposer.ts +0 -265
  145. package/src/generated/l1-contracts-defaults.ts +0 -32
@@ -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
@@ -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
  */
@@ -202,8 +235,35 @@ export type CheckpointProposedArgs = {
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,
@@ -392,16 +450,25 @@ export class RollupContract {
392
450
  epochDuration: number;
393
451
  proofSubmissionEpochs: number;
394
452
  targetCommitteeSize: number;
453
+ rollupManaLimit: number;
395
454
  }> {
396
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs, targetCommitteeSize] =
397
- await Promise.all([
398
- this.getL1StartBlock(),
399
- this.getL1GenesisTime(),
400
- this.getSlotDuration(),
401
- this.getEpochDuration(),
402
- this.getProofSubmissionEpochs(),
403
- this.getTargetCommitteeSize(),
404
- ]);
455
+ const [
456
+ l1StartBlock,
457
+ l1GenesisTime,
458
+ slotDuration,
459
+ epochDuration,
460
+ proofSubmissionEpochs,
461
+ targetCommitteeSize,
462
+ rollupManaLimit,
463
+ ] = await Promise.all([
464
+ this.getL1StartBlock(),
465
+ this.getL1GenesisTime(),
466
+ this.getSlotDuration(),
467
+ this.getEpochDuration(),
468
+ this.getProofSubmissionEpochs(),
469
+ this.getTargetCommitteeSize(),
470
+ this.getManaLimit(),
471
+ ]);
405
472
  return {
406
473
  l1StartBlock,
407
474
  l1GenesisTime,
@@ -409,6 +476,7 @@ export class RollupContract {
409
476
  epochDuration: Number(epochDuration),
410
477
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
411
478
  targetCommitteeSize,
479
+ rollupManaLimit: Number(rollupManaLimit),
412
480
  };
413
481
  }
414
482
 
@@ -463,7 +531,11 @@ export class RollupContract {
463
531
 
464
532
  const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
465
533
  return isOpen;
466
- } 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
+ });
467
539
  return false;
468
540
  }
469
541
  }
@@ -483,8 +555,19 @@ export class RollupContract {
483
555
  return EthAddress.fromString(await this.rollup.read.owner());
484
556
  }
485
557
 
486
- async getActiveAttesterCount(): Promise<number> {
487
- 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));
561
+ }
562
+
563
+ /**
564
+ * Number of attesters that were staked at the given (historical) L1 timestamp. This is the count that
565
+ * validator-set sampling uses to decide whether an epoch gets a committee, and the historical counterpart
566
+ * of {@link getActiveAttesterCount} (which reads at the latest L1 block).
567
+ */
568
+ async getAttesterCountAtTime(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<number> {
569
+ const gse = new GSEContract(this.client, await this.getGSE());
570
+ return gse.getAttesterCountAtTime(this.address, timestamp, options);
488
571
  }
489
572
 
490
573
  public async getSlashingProposerAddress() {
@@ -499,26 +582,41 @@ export class RollupContract {
499
582
  return this.rollup.read.getCheckpointReward();
500
583
  }
501
584
 
502
- async getCheckpointNumber(): Promise<CheckpointNumber> {
503
- return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
585
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
586
+ await checkBlockTag(options?.blockNumber, this.client);
587
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
504
588
  }
505
589
 
506
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
507
- return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber());
590
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
591
+ await checkBlockTag(options?.blockNumber, this.client);
592
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
508
593
  }
509
594
 
510
- async getSlotNumber(): Promise<SlotNumber> {
511
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
595
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
596
+ await checkBlockTag(options?.blockNumber, this.client);
597
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
512
598
  }
513
599
 
514
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
515
- const result = await this.rollup.read.getL1FeesAt([timestamp]);
600
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
601
+ await checkBlockTag(options?.blockNumber, this.client);
602
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
516
603
  return {
517
604
  baseFee: result.baseFee,
518
605
  blobFee: result.blobFee,
519
606
  };
520
607
  }
521
608
 
609
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
610
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
611
+ return {
612
+ excessMana: result.excessMana,
613
+ manaUsed: result.manaUsed,
614
+ ethPerFeeAsset: result.ethPerFeeAsset,
615
+ congestionCost: result.congestionCost,
616
+ proverCost: result.proverCost,
617
+ };
618
+ }
619
+
522
620
  getEthPerFeeAsset(): Promise<bigint> {
523
621
  return this.rollup.read.getEthPerFeeAsset();
524
622
  }
@@ -532,7 +630,7 @@ export class RollupContract {
532
630
  args: [timestamp],
533
631
  })
534
632
  .catch(e => {
535
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
633
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
536
634
  return { result: undefined };
537
635
  }
538
636
  throw e;
@@ -562,7 +660,7 @@ export class RollupContract {
562
660
  args: [],
563
661
  })
564
662
  .catch(e => {
565
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
663
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
566
664
  return { result: undefined };
567
665
  }
568
666
  throw e;
@@ -593,8 +691,9 @@ export class RollupContract {
593
691
  return EthAddress.fromString(result);
594
692
  }
595
693
 
596
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
597
- const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)]);
694
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
695
+ await checkBlockTag(options?.blockNumber, this.client);
696
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
598
697
  return {
599
698
  archive: Fr.fromString(result.archive),
600
699
  headerHash: Buffer32.fromString(result.headerHash),
@@ -627,6 +726,30 @@ export class RollupContract {
627
726
  );
628
727
  }
629
728
 
729
+ /**
730
+ * Returns the effective pending checkpoint, accounting for potential prunes.
731
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
732
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
733
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
734
+ * @param options - Optional L1 block number to pin the queries to.
735
+ */
736
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
737
+ return retry(
738
+ async () => {
739
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
740
+ const canPrune = await this.canPruneAtTime(timestamp, options);
741
+ if (canPrune) {
742
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
743
+ return await this.getCheckpoint(provenCheckpointNumber, options);
744
+ }
745
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
746
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
747
+ },
748
+ 'getting effective pending checkpoint',
749
+ makeBackoff([0.5, 0.5, 0.5]),
750
+ );
751
+ }
752
+
630
753
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
631
754
  const { pending, proven } = await this.rollup.read.getTips();
632
755
  return {
@@ -701,7 +824,7 @@ export class RollupContract {
701
824
  }
702
825
 
703
826
  async getEpochProofPublicInputs(
704
- args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly `0x${string}`[], `0x${string}`],
827
+ args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly ViemHeader[], `0x${string}`],
705
828
  ): Promise<Fr[]> {
706
829
  const result = await this.rollup.read.getEpochProofPublicInputs(args);
707
830
  return result.map(Fr.fromString);
@@ -745,14 +868,13 @@ export class RollupContract {
745
868
  * timestamp of the next L1 block
746
869
  * @throws otherwise
747
870
  */
748
- public async canProposeAtNextEthBlock(
871
+ public async canProposeAt(
749
872
  archive: Buffer,
750
873
  account: `0x${string}` | Account,
751
- slotDuration: number,
752
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
874
+ timestamp: bigint,
875
+ stateOverride: StateOverride = [],
753
876
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
754
- const latestBlock = await this.client.getBlock();
755
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
877
+ const timeOfNextL1Slot = timestamp;
756
878
  const who = typeof account === 'string' ? account : account.address;
757
879
 
758
880
  try {
@@ -764,7 +886,7 @@ export class RollupContract {
764
886
  functionName: 'canProposeAtTime',
765
887
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
766
888
  account,
767
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
889
+ stateOverride,
768
890
  });
769
891
 
770
892
  return {
@@ -778,20 +900,32 @@ export class RollupContract {
778
900
  }
779
901
 
780
902
  /**
781
- * Returns a state override that sets the pending checkpoint number to the specified value. Useful for simulations.
782
- * Requires querying the current state of the contract to get the current proven checkpoint number, as they are both
783
- * stored in the same slot. If the argument is undefined, it returns an empty override.
903
+ * Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
904
+ * Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
905
+ * a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
906
+ * value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
907
+ *
908
+ * Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
909
+ * `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
784
910
  */
785
- public async makePendingCheckpointNumberOverride(
786
- forcePendingCheckpointNumber: CheckpointNumber | undefined,
787
- ): Promise<StateOverride> {
788
- if (forcePendingCheckpointNumber === undefined) {
911
+ public async makeChainTipsOverride(override: {
912
+ pending?: CheckpointNumber;
913
+ proven?: CheckpointNumber;
914
+ }): Promise<StateOverride> {
915
+ if (override.pending === undefined && override.proven === undefined) {
789
916
  return [];
790
917
  }
791
918
  const slot = RollupContract.stfStorageSlot;
792
919
  const currentValue = await this.client.getStorageAt({ address: this.address, slot });
793
- const currentProvenCheckpointNumber = currentValue ? hexToBigInt(currentValue) & ((1n << 128n) - 1n) : 0n;
794
- const newValue = (BigInt(forcePendingCheckpointNumber) << 128n) | currentProvenCheckpointNumber;
920
+ const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
921
+ const currentPending = currentRaw >> 128n;
922
+ const currentProven = currentRaw & ((1n << 128n) - 1n);
923
+ const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
924
+ const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
925
+ if (newProven > newPending) {
926
+ throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
927
+ }
928
+ const newValue = (newPending << 128n) | newProven;
795
929
  return [
796
930
  {
797
931
  address: this.address,
@@ -800,6 +934,168 @@ export class RollupContract {
800
934
  ];
801
935
  }
802
936
 
937
+ /**
938
+ * Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
939
+ * the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
940
+ * pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
941
+ *
942
+ * `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
943
+ * never asserts against them, so leaving them at storage zero is harmless.
944
+ */
945
+ public async makeTempCheckpointLogOverride(
946
+ checkpointNumber: CheckpointNumber,
947
+ fields: TempCheckpointLogOverrideFields,
948
+ ): Promise<StateOverride> {
949
+ const constants = await this.getRollupConstants();
950
+ const slotAt = (field: TempCheckpointLogField) =>
951
+ `0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
952
+ const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
953
+
954
+ const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
955
+
956
+ if (fields.headerHash) {
957
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
958
+ }
959
+ if (fields.outHash) {
960
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
961
+ }
962
+ if (fields.payloadDigest) {
963
+ stateDiff.push({
964
+ slot: slotAt(TempCheckpointLogField.PayloadDigest),
965
+ value: fields.payloadDigest.toString() as `0x${string}`,
966
+ });
967
+ }
968
+ if (fields.slotNumber !== undefined) {
969
+ // CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
970
+ // so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
971
+ const slotNumber = BigInt(fields.slotNumber);
972
+ if (slotNumber < 0n || slotNumber > 0xffffffffn) {
973
+ throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
974
+ }
975
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
976
+ }
977
+ if (fields.feeHeader) {
978
+ stateDiff.push({
979
+ slot: slotAt(TempCheckpointLogField.FeeHeader),
980
+ value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
981
+ });
982
+ }
983
+
984
+ if (stateDiff.length === 0) {
985
+ return [];
986
+ }
987
+ return [{ address: this.address, stateDiff }];
988
+ }
989
+
990
+ /**
991
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
992
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
993
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
994
+ */
995
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
996
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
997
+ const archiveSlot = hexToBigInt(
998
+ keccak256(
999
+ encodeAbiParameters(
1000
+ [{ type: 'uint256' }, { type: 'uint256' }],
1001
+ [BigInt(checkpointNumber), archivesMappingBase],
1002
+ ),
1003
+ ),
1004
+ );
1005
+ return [
1006
+ {
1007
+ address: this.address,
1008
+ stateDiff: [
1009
+ {
1010
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
1011
+ value: archive.toString(),
1012
+ },
1013
+ ],
1014
+ },
1015
+ ];
1016
+ }
1017
+
1018
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
1019
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
1020
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
1021
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
1022
+ for (const override of overrides) {
1023
+ for (const entry of override) {
1024
+ const key = entry.address.toLowerCase();
1025
+ const existing = byAddress.get(key);
1026
+ if (existing) {
1027
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
1028
+ if (entry.balance !== undefined) {
1029
+ existing.balance = entry.balance;
1030
+ }
1031
+ } else {
1032
+ byAddress.set(key, {
1033
+ address: entry.address,
1034
+ balance: entry.balance,
1035
+ stateDiff: [...(entry.stateDiff ?? [])],
1036
+ });
1037
+ }
1038
+ }
1039
+ }
1040
+ return [...byAddress.values()];
1041
+ }
1042
+
1043
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
1044
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
1045
+ const MASK_48_BITS = (1n << 48n) - 1n;
1046
+ const MASK_64_BITS = (1n << 64n) - 1n;
1047
+ const MASK_63_BITS = (1n << 63n) - 1n;
1048
+
1049
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
1050
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
1051
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
1052
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
1053
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
1054
+ value |= 1n << 255n; // preheat flag
1055
+ return value;
1056
+ }
1057
+
1058
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
1059
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
1060
+ public static computeChildFeeHeader(
1061
+ parentFeeHeader: FeeHeader,
1062
+ childManaUsed: bigint,
1063
+ feeAssetPriceModifier: bigint,
1064
+ manaTarget: bigint,
1065
+ ): FeeHeader {
1066
+ const MIN_ETH_PER_FEE_ASSET = 100n;
1067
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
1068
+
1069
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
1070
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
1071
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
1072
+
1073
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
1074
+ const parentPrice =
1075
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
1076
+ let newPrice: bigint;
1077
+ if (feeAssetPriceModifier >= 0n) {
1078
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
1079
+ } else {
1080
+ const absMod = -feeAssetPriceModifier;
1081
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
1082
+ }
1083
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
1084
+ newPrice = MIN_ETH_PER_FEE_ASSET;
1085
+ }
1086
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1087
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1088
+ }
1089
+
1090
+ return {
1091
+ excessMana,
1092
+ manaUsed: childManaUsed,
1093
+ ethPerFeeAsset: newPrice,
1094
+ congestionCost: 0n,
1095
+ proverCost: 0n,
1096
+ };
1097
+ }
1098
+
803
1099
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
804
1100
  public buildInvalidateBadAttestationRequest(
805
1101
  checkpointNumber: CheckpointNumber,
@@ -848,8 +1144,18 @@ export class RollupContract {
848
1144
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
849
1145
  }
850
1146
 
851
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
852
- return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
1147
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1148
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
1149
+ }
1150
+
1151
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1152
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1153
+ return {
1154
+ sequencerCost: result.sequencerCost,
1155
+ proverCost: result.proverCost,
1156
+ congestionCost: result.congestionCost,
1157
+ congestionMultiplier: result.congestionMultiplier,
1158
+ };
853
1159
  }
854
1160
 
855
1161
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -895,15 +1201,21 @@ export class RollupContract {
895
1201
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
896
1202
  }
897
1203
 
898
- async getAttesters(): Promise<EthAddress[]> {
899
- const attesterSize = await this.getActiveAttesterCount();
1204
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
1205
+ // Pin every read to a single L1 block so the attester count and the chunked index reads
1206
+ // observe a consistent set. Without this, the count and each chunk default to `latest` and
1207
+ // can straddle a block boundary (or reorg), yielding an inconsistent or truncated set.
1208
+ const block = await this.client.getBlock();
1209
+ const blockNumber = block.number ?? undefined;
1210
+ const ts = timestamp ?? block.timestamp;
1211
+ const attesterSize = await this.getActiveAttesterCount({ blockNumber });
900
1212
  const gse = new GSEContract(this.client, await this.getGSE());
901
- const ts = (await this.client.getBlock()).timestamp;
902
-
903
1213
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
904
1214
  const chunks = chunk(indices, 1000);
905
1215
 
906
- const results = await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)));
1216
+ const results = await Promise.all(
1217
+ chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, { blockNumber })),
1218
+ );
907
1219
  return results.flat().map(addr => EthAddress.fromString(addr));
908
1220
  }
909
1221
 
@@ -1005,7 +1317,7 @@ export class RollupContract {
1005
1317
  }
1006
1318
 
1007
1319
  public listenToCheckpointInvalidated(
1008
- callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
1320
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
1009
1321
  ): WatchContractEventReturnType {
1010
1322
  return this.rollup.watchEvent.CheckpointInvalidated(
1011
1323
  {},
@@ -1014,7 +1326,7 @@ export class RollupContract {
1014
1326
  for (const log of logs) {
1015
1327
  const args = log.args;
1016
1328
  if (args.checkpointNumber !== undefined) {
1017
- callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
1329
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
1018
1330
  }
1019
1331
  }
1020
1332
  },
@@ -1047,6 +1359,17 @@ export class RollupContract {
1047
1359
  );
1048
1360
  }
1049
1361
 
1362
+ /**
1363
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1364
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1365
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1366
+ * probe to detect RPC nodes that prune historical logs.
1367
+ */
1368
+ async getOwnershipTransferredEventsAtDeploy() {
1369
+ const l1StartBlock = await this.getL1StartBlock();
1370
+ return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
1371
+ }
1372
+
1050
1373
  /** Fetches CheckpointProposed events within the given block range. */
1051
1374
  async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1052
1375
  const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
@@ -1079,4 +1402,48 @@ export class RollupContract {
1079
1402
  },
1080
1403
  }));
1081
1404
  }
1405
+
1406
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1407
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1408
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1409
+ }
1410
+
1411
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1412
+ static get chainTipsStorageSlot(): bigint {
1413
+ return BigInt(RollupContract.stfStorageSlot);
1414
+ }
1415
+
1416
+ /**
1417
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1418
+ * @param checkpointNumber - The checkpoint number
1419
+ * @param field - The field within the CompressedTempCheckpointLog struct
1420
+ */
1421
+ async getTempCheckpointLogStorageSlot(
1422
+ checkpointNumber: CheckpointNumber,
1423
+ field: TempCheckpointLogField,
1424
+ ): Promise<bigint> {
1425
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1426
+ this.getEpochDuration(),
1427
+ this.getProofSubmissionEpochs(),
1428
+ ]);
1429
+ return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
1430
+ }
1431
+
1432
+ private computeTempCheckpointLogStorageSlot(
1433
+ checkpointNumber: CheckpointNumber,
1434
+ field: TempCheckpointLogField,
1435
+ constants: { epochDuration: number; proofSubmissionEpochs: number },
1436
+ ): bigint {
1437
+ const fieldOffset = BigInt(field);
1438
+ const { epochDuration, proofSubmissionEpochs } = constants;
1439
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1440
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1441
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1442
+ const entryBase = BigInt(
1443
+ keccak256(
1444
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1445
+ ),
1446
+ );
1447
+ return entryBase + fieldOffset;
1448
+ }
1082
1449
  }