@aztec/ethereum 0.0.1-commit.04852196a → 0.0.1-commit.04d373f

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 (116) 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 +6 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +5 -9
  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 +108 -33
  16. package/dest/contracts/governance.d.ts.map +1 -1
  17. package/dest/contracts/governance.js +193 -12
  18. package/dest/contracts/governance_proposer.d.ts +33 -3
  19. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  20. package/dest/contracts/governance_proposer.js +51 -9
  21. package/dest/contracts/inbox.d.ts +3 -3
  22. package/dest/contracts/inbox.d.ts.map +1 -1
  23. package/dest/contracts/inbox.js +12 -7
  24. package/dest/contracts/index.d.ts +3 -3
  25. package/dest/contracts/index.d.ts.map +1 -1
  26. package/dest/contracts/index.js +2 -2
  27. package/dest/contracts/multicall.d.ts +127 -12
  28. package/dest/contracts/multicall.d.ts.map +1 -1
  29. package/dest/contracts/multicall.js +201 -94
  30. package/dest/contracts/outbox.d.ts +21 -4
  31. package/dest/contracts/outbox.d.ts.map +1 -1
  32. package/dest/contracts/outbox.js +29 -6
  33. package/dest/contracts/rollup.d.ts +5254 -25
  34. package/dest/contracts/rollup.d.ts.map +1 -1
  35. package/dest/contracts/rollup.js +350 -67
  36. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
  37. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  38. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +13 -15
  39. package/dest/deploy_aztec_l1_contracts.d.ts +3 -6
  40. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  41. package/dest/deploy_aztec_l1_contracts.js +2 -4
  42. package/dest/generated/l1-contracts-defaults.d.ts +1 -1
  43. package/dest/generated/l1-contracts-defaults.js +1 -1
  44. package/dest/l1_artifacts.d.ts +13409 -15883
  45. package/dest/l1_artifacts.d.ts.map +1 -1
  46. package/dest/l1_artifacts.js +9 -24
  47. package/dest/l1_contract_addresses.d.ts +61 -65
  48. package/dest/l1_contract_addresses.d.ts.map +1 -1
  49. package/dest/l1_contract_addresses.js +101 -82
  50. package/dest/l1_reader.d.ts +5 -5
  51. package/dest/l1_reader.d.ts.map +1 -1
  52. package/dest/l1_reader.js +9 -7
  53. package/dest/l1_tx_utils/l1_tx_utils.d.ts +9 -1
  54. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  55. package/dest/l1_tx_utils/l1_tx_utils.js +54 -35
  56. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  57. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  58. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +8 -4
  59. package/dest/l1_tx_utils/tx_delayer.d.ts +5 -3
  60. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -1
  61. package/dest/l1_tx_utils/tx_delayer.js +7 -5
  62. package/dest/publisher_manager.d.ts +21 -7
  63. package/dest/publisher_manager.d.ts.map +1 -1
  64. package/dest/publisher_manager.js +81 -7
  65. package/dest/queries.d.ts +13 -2
  66. package/dest/queries.d.ts.map +1 -1
  67. package/dest/queries.js +62 -3
  68. package/dest/test/chain_monitor.d.ts +23 -3
  69. package/dest/test/chain_monitor.d.ts.map +1 -1
  70. package/dest/test/chain_monitor.js +38 -2
  71. package/dest/test/eth_cheat_codes.d.ts +12 -4
  72. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  73. package/dest/test/eth_cheat_codes.js +17 -4
  74. package/dest/test/rollup_cheat_codes.d.ts +31 -3
  75. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  76. package/dest/test/rollup_cheat_codes.js +59 -7
  77. package/dest/test/start_anvil.d.ts +15 -1
  78. package/dest/test/start_anvil.d.ts.map +1 -1
  79. package/dest/test/start_anvil.js +17 -2
  80. package/dest/utils.d.ts +8 -1
  81. package/dest/utils.d.ts.map +1 -1
  82. package/dest/utils.js +19 -0
  83. package/package.json +6 -6
  84. package/src/client.ts +28 -2
  85. package/src/config.ts +9 -13
  86. package/src/contracts/chain_state_override.ts +200 -0
  87. package/src/contracts/empire_base.ts +0 -2
  88. package/src/contracts/fee_asset_price_oracle.ts +10 -5
  89. package/src/contracts/governance.ts +266 -9
  90. package/src/contracts/governance_proposer.ts +52 -5
  91. package/src/contracts/inbox.ts +13 -5
  92. package/src/contracts/index.ts +2 -2
  93. package/src/contracts/multicall.ts +243 -105
  94. package/src/contracts/outbox.ts +38 -8
  95. package/src/contracts/rollup.ts +391 -73
  96. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +14 -16
  97. package/src/deploy_aztec_l1_contracts.ts +1 -5
  98. package/src/generated/l1-contracts-defaults.ts +1 -1
  99. package/src/l1_artifacts.ts +12 -35
  100. package/src/l1_contract_addresses.ts +120 -94
  101. package/src/l1_reader.ts +17 -10
  102. package/src/l1_tx_utils/l1_tx_utils.ts +51 -28
  103. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +8 -4
  104. package/src/l1_tx_utils/tx_delayer.ts +16 -7
  105. package/src/publisher_manager.ts +105 -10
  106. package/src/queries.ts +70 -4
  107. package/src/test/chain_monitor.ts +65 -3
  108. package/src/test/eth_cheat_codes.ts +16 -4
  109. package/src/test/rollup_cheat_codes.ts +64 -6
  110. package/src/test/start_anvil.ts +33 -2
  111. package/src/utils.ts +18 -0
  112. package/dest/contracts/empire_slashing_proposer.d.ts +0 -69
  113. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  114. package/dest/contracts/empire_slashing_proposer.js +0 -216
  115. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  116. package/src/contracts/empire_slashing_proposer.ts +0 -265
@@ -4,6 +4,7 @@ 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';
8
9
  import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
9
10
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
@@ -14,8 +15,10 @@ import {
14
15
  type Account,
15
16
  type GetContractReturnType,
16
17
  type Hex,
18
+ type Log,
17
19
  type StateOverride,
18
20
  type WatchContractEventReturnType,
21
+ encodeAbiParameters,
19
22
  encodeFunctionData,
20
23
  getContract,
21
24
  hexToBigInt,
@@ -29,11 +32,10 @@ import type { L1ReaderConfig } from '../l1_reader.js';
29
32
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
30
33
  import type { ViemClient } from '../types.js';
31
34
  import { formatViemError } from '../utils.js';
32
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
33
35
  import { GSEContract } from './gse.js';
34
36
  import type { L1EventLog } from './log.js';
35
37
  import { SlasherContract } from './slasher_contract.js';
36
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
38
+ import { SlashingProposerContract } from './slashing_proposer.js';
37
39
  import { checkBlockTag } from './utils.js';
38
40
 
39
41
  export type ViemCommitteeAttestation = {
@@ -55,7 +57,6 @@ export type L1RollupContractAddresses = Pick<
55
57
  | 'feeJuiceAddress'
56
58
  | 'stakingAssetAddress'
57
59
  | 'rewardDistributorAddress'
58
- | 'slashFactoryAddress'
59
60
  | 'gseAddress'
60
61
  >;
61
62
 
@@ -85,12 +86,6 @@ export type ViemGasFees = {
85
86
  feePerL2Gas: bigint;
86
87
  };
87
88
 
88
- export enum SlashingProposerType {
89
- None = 0,
90
- Tally = 1,
91
- Empire = 2,
92
- }
93
-
94
89
  /**
95
90
  * Status of a validator/attester in the staking system.
96
91
  * Matches the Status enum in StakingLib.sol
@@ -134,6 +129,39 @@ export type L1FeeData = {
134
129
  blobFee: bigint;
135
130
  };
136
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
+
137
165
  /**
138
166
  * Reward configuration for the rollup
139
167
  */
@@ -204,6 +232,7 @@ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
204
232
 
205
233
  export class RollupContract {
206
234
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
235
+ private readonly logger = createLogger('ethereum:rollup');
207
236
 
208
237
  private static cachedStfStorageSlot: Hex | undefined;
209
238
  private cachedEscapeHatch?: {
@@ -233,7 +262,7 @@ export class RollupContract {
233
262
 
234
263
  static getFromConfig(config: L1ReaderConfig) {
235
264
  const client = getPublicClient(config);
236
- const address = config.l1Contracts.rollupAddress.toString();
265
+ const address = config.rollupAddress.toString();
237
266
  return new RollupContract(client, address);
238
267
  }
239
268
 
@@ -259,34 +288,18 @@ export class RollupContract {
259
288
  return this.rollup;
260
289
  }
261
290
 
262
- public async getSlashingProposer(): Promise<
263
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
264
- > {
291
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
265
292
  const slasher = await this.getSlasherContract();
266
293
  if (!slasher) {
267
294
  return undefined;
268
295
  }
269
296
 
270
297
  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}`);
298
+ if (proposerAddress.isZero()) {
299
+ return undefined;
289
300
  }
301
+
302
+ return new SlashingProposerContract(this.client, proposerAddress);
290
303
  }
291
304
 
292
305
  @memoize
@@ -379,6 +392,20 @@ export class RollupContract {
379
392
  return Fr.fromString(await this.rollup.read.archiveAt([0n]));
380
393
  }
381
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
+
382
409
  /**
383
410
  * Returns rollup constants used for epoch queries.
384
411
  * Return type is `L1RollupConstants` which is defined in stdlib,
@@ -392,16 +419,25 @@ export class RollupContract {
392
419
  epochDuration: number;
393
420
  proofSubmissionEpochs: number;
394
421
  targetCommitteeSize: number;
422
+ rollupManaLimit: number;
395
423
  }> {
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
- ]);
424
+ const [
425
+ l1StartBlock,
426
+ l1GenesisTime,
427
+ slotDuration,
428
+ epochDuration,
429
+ proofSubmissionEpochs,
430
+ targetCommitteeSize,
431
+ rollupManaLimit,
432
+ ] = await Promise.all([
433
+ this.getL1StartBlock(),
434
+ this.getL1GenesisTime(),
435
+ this.getSlotDuration(),
436
+ this.getEpochDuration(),
437
+ this.getProofSubmissionEpochs(),
438
+ this.getTargetCommitteeSize(),
439
+ this.getManaLimit(),
440
+ ]);
405
441
  return {
406
442
  l1StartBlock,
407
443
  l1GenesisTime,
@@ -409,6 +445,7 @@ export class RollupContract {
409
445
  epochDuration: Number(epochDuration),
410
446
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
411
447
  targetCommitteeSize,
448
+ rollupManaLimit: Number(rollupManaLimit),
412
449
  };
413
450
  }
414
451
 
@@ -463,7 +500,11 @@ export class RollupContract {
463
500
 
464
501
  const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
465
502
  return isOpen;
466
- } catch {
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
+ });
467
508
  return false;
468
509
  }
469
510
  }
@@ -499,26 +540,41 @@ export class RollupContract {
499
540
  return this.rollup.read.getCheckpointReward();
500
541
  }
501
542
 
502
- async getCheckpointNumber(): Promise<CheckpointNumber> {
503
- 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));
504
546
  }
505
547
 
506
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
507
- 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));
508
551
  }
509
552
 
510
- async getSlotNumber(): Promise<SlotNumber> {
511
- 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));
512
556
  }
513
557
 
514
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
515
- 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);
516
561
  return {
517
562
  baseFee: result.baseFee,
518
563
  blobFee: result.blobFee,
519
564
  };
520
565
  }
521
566
 
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
+
522
578
  getEthPerFeeAsset(): Promise<bigint> {
523
579
  return this.rollup.read.getEthPerFeeAsset();
524
580
  }
@@ -593,8 +649,9 @@ export class RollupContract {
593
649
  return EthAddress.fromString(result);
594
650
  }
595
651
 
596
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
597
- 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);
598
655
  return {
599
656
  archive: Fr.fromString(result.archive),
600
657
  headerHash: Buffer32.fromString(result.headerHash),
@@ -627,6 +684,30 @@ export class RollupContract {
627
684
  );
628
685
  }
629
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
+
630
711
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
631
712
  const { pending, proven } = await this.rollup.read.getTips();
632
713
  return {
@@ -745,14 +826,13 @@ export class RollupContract {
745
826
  * timestamp of the next L1 block
746
827
  * @throws otherwise
747
828
  */
748
- public async canProposeAtNextEthBlock(
829
+ public async canProposeAt(
749
830
  archive: Buffer,
750
831
  account: `0x${string}` | Account,
751
- slotDuration: number,
752
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
832
+ timestamp: bigint,
833
+ stateOverride: StateOverride = [],
753
834
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
754
- const latestBlock = await this.client.getBlock();
755
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
835
+ const timeOfNextL1Slot = timestamp;
756
836
  const who = typeof account === 'string' ? account : account.address;
757
837
 
758
838
  try {
@@ -764,7 +844,7 @@ export class RollupContract {
764
844
  functionName: 'canProposeAtTime',
765
845
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
766
846
  account,
767
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
847
+ stateOverride,
768
848
  });
769
849
 
770
850
  return {
@@ -778,20 +858,32 @@ export class RollupContract {
778
858
  }
779
859
 
780
860
  /**
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.
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`.
784
868
  */
785
- public async makePendingCheckpointNumberOverride(
786
- forcePendingCheckpointNumber: CheckpointNumber | undefined,
787
- ): Promise<StateOverride> {
788
- 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) {
789
874
  return [];
790
875
  }
791
876
  const slot = RollupContract.stfStorageSlot;
792
877
  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;
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;
795
887
  return [
796
888
  {
797
889
  address: this.address,
@@ -800,6 +892,168 @@ export class RollupContract {
800
892
  ];
801
893
  }
802
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
+
803
1057
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
804
1058
  public buildInvalidateBadAttestationRequest(
805
1059
  checkpointNumber: CheckpointNumber,
@@ -848,8 +1102,18 @@ export class RollupContract {
848
1102
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
849
1103
  }
850
1104
 
851
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
852
- 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
+ };
853
1117
  }
854
1118
 
855
1119
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -895,11 +1159,10 @@ export class RollupContract {
895
1159
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
896
1160
  }
897
1161
 
898
- async getAttesters(): Promise<EthAddress[]> {
1162
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
899
1163
  const attesterSize = await this.getActiveAttesterCount();
900
1164
  const gse = new GSEContract(this.client, await this.getGSE());
901
- const ts = (await this.client.getBlock()).timestamp;
902
-
1165
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
903
1166
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
904
1167
  const chunks = chunk(indices, 1000);
905
1168
 
@@ -1005,7 +1268,7 @@ export class RollupContract {
1005
1268
  }
1006
1269
 
1007
1270
  public listenToCheckpointInvalidated(
1008
- callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
1271
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
1009
1272
  ): WatchContractEventReturnType {
1010
1273
  return this.rollup.watchEvent.CheckpointInvalidated(
1011
1274
  {},
@@ -1014,7 +1277,7 @@ export class RollupContract {
1014
1277
  for (const log of logs) {
1015
1278
  const args = log.args;
1016
1279
  if (args.checkpointNumber !== undefined) {
1017
- callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
1280
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
1018
1281
  }
1019
1282
  }
1020
1283
  },
@@ -1047,6 +1310,17 @@ export class RollupContract {
1047
1310
  );
1048
1311
  }
1049
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
+
1050
1324
  /** Fetches CheckpointProposed events within the given block range. */
1051
1325
  async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1052
1326
  const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
@@ -1079,4 +1353,48 @@ export class RollupContract {
1079
1353
  },
1080
1354
  }));
1081
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
+ }
1082
1400
  }