@aztec/ethereum 0.0.1-commit.4d3c002 → 0.0.1-commit.4d9804df

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 (143) hide show
  1. package/dest/client.d.ts +17 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +49 -3
  4. package/dest/config.d.ts +27 -8
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +59 -12
  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/gse.d.ts +4 -2
  22. package/dest/contracts/gse.d.ts.map +1 -1
  23. package/dest/contracts/gse.js +2 -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 +9 -3
  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 +82 -14
  31. package/dest/contracts/multicall.d.ts.map +1 -1
  32. package/dest/contracts/multicall.js +119 -95
  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 +5383 -31
  37. package/dest/contracts/rollup.d.ts.map +1 -1
  38. package/dest/contracts/rollup.js +223 -90
  39. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
  40. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  41. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +13 -15
  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 +14941 -16273
  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 +3 -5
  55. package/dest/l1_reader.d.ts.map +1 -1
  56. package/dest/l1_reader.js +3 -6
  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 +7 -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 +105 -32
  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 +2 -1
  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 +18 -6
  74. package/dest/publisher_manager.d.ts.map +1 -1
  75. package/dest/publisher_manager.js +44 -7
  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 +34 -2
  83. package/dest/test/chain_monitor.d.ts.map +1 -1
  84. package/dest/test/chain_monitor.js +76 -6
  85. package/dest/test/eth_cheat_codes.d.ts +32 -3
  86. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  87. package/dest/test/eth_cheat_codes.js +83 -37
  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 +1 -1
  95. package/dest/test/start_anvil.d.ts.map +1 -1
  96. package/dest/test/start_anvil.js +41 -5
  97. package/dest/utils.d.ts +9 -2
  98. package/dest/utils.d.ts.map +1 -1
  99. package/dest/utils.js +34 -6
  100. package/package.json +6 -7
  101. package/src/client.ts +57 -1
  102. package/src/config.ts +82 -16
  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 +266 -9
  107. package/src/contracts/governance_proposer.ts +52 -5
  108. package/src/contracts/gse.ts +7 -2
  109. package/src/contracts/inbox.ts +12 -4
  110. package/src/contracts/index.ts +2 -2
  111. package/src/contracts/multicall.ts +183 -106
  112. package/src/contracts/outbox.ts +42 -8
  113. package/src/contracts/rollup.ts +252 -96
  114. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +14 -16
  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 +4 -9
  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 +98 -34
  123. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +2 -1
  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 +54 -18
  127. package/src/queries.ts +76 -4
  128. package/src/test/blob_kzg_warmup.ts +65 -0
  129. package/src/test/chain_monitor.ts +93 -5
  130. package/src/test/eth_cheat_codes.ts +76 -41
  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 +38 -6
  134. package/src/utils.ts +30 -6
  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.map +0 -1
  139. package/dest/generated/l1-contracts-defaults.d.ts +0 -30
  140. package/dest/generated/l1-contracts-defaults.d.ts.map +0 -1
  141. package/dest/generated/l1-contracts-defaults.js +0 -30
  142. package/src/contracts/empire_slashing_proposer.ts +0 -265
  143. package/src/generated/l1-contracts-defaults.ts +0 -32
@@ -6,6 +6,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
7
  import { createLogger } from '@aztec/foundation/log';
8
8
  import { makeBackoff, retry } from '@aztec/foundation/retry';
9
+ import { getErrorCause } from '@aztec/foundation/types';
9
10
  import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
10
11
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
11
12
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
@@ -13,10 +14,14 @@ import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
13
14
  import chunk from 'lodash.chunk';
14
15
  import {
15
16
  type Account,
17
+ ContractFunctionRevertedError,
16
18
  type GetContractReturnType,
17
19
  type Hex,
20
+ type Log,
21
+ RpcRequestError,
18
22
  type StateOverride,
19
23
  type WatchContractEventReturnType,
24
+ decodeErrorResult,
20
25
  encodeAbiParameters,
21
26
  encodeFunctionData,
22
27
  getContract,
@@ -31,11 +36,10 @@ import type { L1ReaderConfig } from '../l1_reader.js';
31
36
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
32
37
  import type { ViemClient } from '../types.js';
33
38
  import { formatViemError } from '../utils.js';
34
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
35
39
  import { GSEContract } from './gse.js';
36
40
  import type { L1EventLog } from './log.js';
37
41
  import { SlasherContract } from './slasher_contract.js';
38
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
42
+ import { SlashingProposerContract } from './slashing_proposer.js';
39
43
  import { checkBlockTag } from './utils.js';
40
44
 
41
45
  export type ViemCommitteeAttestation = {
@@ -57,7 +61,6 @@ export type L1RollupContractAddresses = Pick<
57
61
  | 'feeJuiceAddress'
58
62
  | 'stakingAssetAddress'
59
63
  | 'rewardDistributorAddress'
60
- | 'slashFactoryAddress'
61
64
  | 'gseAddress'
62
65
  >;
63
66
 
@@ -80,6 +83,7 @@ export type ViemHeader = {
80
83
  feeRecipient: `0x${string}`;
81
84
  gasFees: ViemGasFees;
82
85
  totalManaUsed: bigint;
86
+ accumulatedFees: bigint;
83
87
  };
84
88
 
85
89
  export type ViemGasFees = {
@@ -87,12 +91,6 @@ export type ViemGasFees = {
87
91
  feePerL2Gas: bigint;
88
92
  };
89
93
 
90
- export enum SlashingProposerType {
91
- None = 0,
92
- Tally = 1,
93
- Empire = 2,
94
- }
95
-
96
94
  /**
97
95
  * Status of a validator/attester in the staking system.
98
96
  * Matches the Status enum in StakingLib.sol
@@ -136,6 +134,31 @@ export type L1FeeData = {
136
134
  blobFee: bigint;
137
135
  };
138
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
+
139
162
  /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
140
163
  export type ManaMinFeeComponents = {
141
164
  sequencerCost: bigint;
@@ -212,6 +235,32 @@ export type CheckpointProposedArgs = {
212
235
  /** Log type for CheckpointProposed events. */
213
236
  export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
214
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
+
215
264
  export class RollupContract {
216
265
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
217
266
  private readonly logger = createLogger('ethereum:rollup');
@@ -244,7 +293,7 @@ export class RollupContract {
244
293
 
245
294
  static getFromConfig(config: L1ReaderConfig) {
246
295
  const client = getPublicClient(config);
247
- const address = config.l1Contracts.rollupAddress.toString();
296
+ const address = config.rollupAddress.toString();
248
297
  return new RollupContract(client, address);
249
298
  }
250
299
 
@@ -270,34 +319,18 @@ export class RollupContract {
270
319
  return this.rollup;
271
320
  }
272
321
 
273
- public async getSlashingProposer(): Promise<
274
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
275
- > {
322
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
276
323
  const slasher = await this.getSlasherContract();
277
324
  if (!slasher) {
278
325
  return undefined;
279
326
  }
280
327
 
281
328
  const proposerAddress = await slasher.getProposer();
282
- const proposerAbi = [
283
- {
284
- type: 'function',
285
- name: 'SLASHING_PROPOSER_TYPE',
286
- inputs: [],
287
- outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
288
- stateMutability: 'view',
289
- },
290
- ] as const;
291
-
292
- const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
293
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
294
- if (proposerType === SlashingProposerType.Tally.valueOf()) {
295
- return new TallySlashingProposerContract(this.client, proposerAddress);
296
- } else if (proposerType === SlashingProposerType.Empire.valueOf()) {
297
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
298
- } else {
299
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
329
+ if (proposerAddress.isZero()) {
330
+ return undefined;
300
331
  }
332
+
333
+ return new SlashingProposerContract(this.client, proposerAddress);
301
334
  }
302
335
 
303
336
  @memoize
@@ -522,8 +555,9 @@ export class RollupContract {
522
555
  return EthAddress.fromString(await this.rollup.read.owner());
523
556
  }
524
557
 
525
- async getActiveAttesterCount(): Promise<number> {
526
- 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));
527
561
  }
528
562
 
529
563
  public async getSlashingProposerAddress() {
@@ -538,8 +572,9 @@ export class RollupContract {
538
572
  return this.rollup.read.getCheckpointReward();
539
573
  }
540
574
 
541
- async getCheckpointNumber(): Promise<CheckpointNumber> {
542
- return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
575
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
576
+ await checkBlockTag(options?.blockNumber, this.client);
577
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
543
578
  }
544
579
 
545
580
  async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
@@ -547,18 +582,31 @@ export class RollupContract {
547
582
  return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
548
583
  }
549
584
 
550
- async getSlotNumber(): Promise<SlotNumber> {
551
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
585
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
586
+ await checkBlockTag(options?.blockNumber, this.client);
587
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
552
588
  }
553
589
 
554
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
555
- const result = await this.rollup.read.getL1FeesAt([timestamp]);
590
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
591
+ await checkBlockTag(options?.blockNumber, this.client);
592
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
556
593
  return {
557
594
  baseFee: result.baseFee,
558
595
  blobFee: result.blobFee,
559
596
  };
560
597
  }
561
598
 
599
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
600
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
601
+ return {
602
+ excessMana: result.excessMana,
603
+ manaUsed: result.manaUsed,
604
+ ethPerFeeAsset: result.ethPerFeeAsset,
605
+ congestionCost: result.congestionCost,
606
+ proverCost: result.proverCost,
607
+ };
608
+ }
609
+
562
610
  getEthPerFeeAsset(): Promise<bigint> {
563
611
  return this.rollup.read.getEthPerFeeAsset();
564
612
  }
@@ -572,7 +620,7 @@ export class RollupContract {
572
620
  args: [timestamp],
573
621
  })
574
622
  .catch(e => {
575
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
623
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
576
624
  return { result: undefined };
577
625
  }
578
626
  throw e;
@@ -602,7 +650,7 @@ export class RollupContract {
602
650
  args: [],
603
651
  })
604
652
  .catch(e => {
605
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
653
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
606
654
  return { result: undefined };
607
655
  }
608
656
  throw e;
@@ -633,8 +681,9 @@ export class RollupContract {
633
681
  return EthAddress.fromString(result);
634
682
  }
635
683
 
636
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
637
- const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)]);
684
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
685
+ await checkBlockTag(options?.blockNumber, this.client);
686
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
638
687
  return {
639
688
  archive: Fr.fromString(result.archive),
640
689
  headerHash: Buffer32.fromString(result.headerHash),
@@ -667,6 +716,30 @@ export class RollupContract {
667
716
  );
668
717
  }
669
718
 
719
+ /**
720
+ * Returns the effective pending checkpoint, accounting for potential prunes.
721
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
722
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
723
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
724
+ * @param options - Optional L1 block number to pin the queries to.
725
+ */
726
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
727
+ return retry(
728
+ async () => {
729
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
730
+ const canPrune = await this.canPruneAtTime(timestamp, options);
731
+ if (canPrune) {
732
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
733
+ return await this.getCheckpoint(provenCheckpointNumber, options);
734
+ }
735
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
736
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
737
+ },
738
+ 'getting effective pending checkpoint',
739
+ makeBackoff([0.5, 0.5, 0.5]),
740
+ );
741
+ }
742
+
670
743
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
671
744
  const { pending, proven } = await this.rollup.read.getTips();
672
745
  return {
@@ -741,7 +814,7 @@ export class RollupContract {
741
814
  }
742
815
 
743
816
  async getEpochProofPublicInputs(
744
- args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly `0x${string}`[], `0x${string}`],
817
+ args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly ViemHeader[], `0x${string}`],
745
818
  ): Promise<Fr[]> {
746
819
  const result = await this.rollup.read.getEpochProofPublicInputs(args);
747
820
  return result.map(Fr.fromString);
@@ -789,19 +862,11 @@ export class RollupContract {
789
862
  archive: Buffer,
790
863
  account: `0x${string}` | Account,
791
864
  timestamp: bigint,
792
- opts: {
793
- forcePendingCheckpointNumber?: CheckpointNumber;
794
- forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
795
- } = {},
865
+ stateOverride: StateOverride = [],
796
866
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
797
867
  const timeOfNextL1Slot = timestamp;
798
868
  const who = typeof account === 'string' ? account : account.address;
799
869
 
800
- const stateOverride = RollupContract.mergeStateOverrides(
801
- await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
802
- opts.forceArchive ? this.makeArchiveOverride(opts.forceArchive.checkpointNumber, opts.forceArchive.archive) : [],
803
- );
804
-
805
870
  try {
806
871
  const {
807
872
  result: [slot, checkpointNumber],
@@ -825,20 +890,32 @@ export class RollupContract {
825
890
  }
826
891
 
827
892
  /**
828
- * Returns a state override that sets the pending checkpoint number to the specified value. Useful for simulations.
829
- * Requires querying the current state of the contract to get the current proven checkpoint number, as they are both
830
- * stored in the same slot. If the argument is undefined, it returns an empty override.
893
+ * Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
894
+ * Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
895
+ * a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
896
+ * value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
897
+ *
898
+ * Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
899
+ * `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
831
900
  */
832
- public async makePendingCheckpointNumberOverride(
833
- forcePendingCheckpointNumber: CheckpointNumber | undefined,
834
- ): Promise<StateOverride> {
835
- if (forcePendingCheckpointNumber === undefined) {
901
+ public async makeChainTipsOverride(override: {
902
+ pending?: CheckpointNumber;
903
+ proven?: CheckpointNumber;
904
+ }): Promise<StateOverride> {
905
+ if (override.pending === undefined && override.proven === undefined) {
836
906
  return [];
837
907
  }
838
908
  const slot = RollupContract.stfStorageSlot;
839
909
  const currentValue = await this.client.getStorageAt({ address: this.address, slot });
840
- const currentProvenCheckpointNumber = currentValue ? hexToBigInt(currentValue) & ((1n << 128n) - 1n) : 0n;
841
- const newValue = (BigInt(forcePendingCheckpointNumber) << 128n) | currentProvenCheckpointNumber;
910
+ const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
911
+ const currentPending = currentRaw >> 128n;
912
+ const currentProven = currentRaw & ((1n << 128n) - 1n);
913
+ const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
914
+ const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
915
+ if (newProven > newPending) {
916
+ throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
917
+ }
918
+ const newValue = (newPending << 128n) | newProven;
842
919
  return [
843
920
  {
844
921
  address: this.address,
@@ -848,39 +925,56 @@ export class RollupContract {
848
925
  }
849
926
 
850
927
  /**
851
- * Returns a state override that sets tempCheckpointLogs[checkpointNumber].feeHeader to the compressed fee header.
852
- * Used when simulating a propose call where the parent checkpoint hasn't landed on L1 yet (pipelining).
928
+ * Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
929
+ * the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
930
+ * pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
931
+ *
932
+ * `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
933
+ * never asserts against them, so leaving them at storage zero is harmless.
853
934
  */
854
- public async makeFeeHeaderOverride(checkpointNumber: CheckpointNumber, feeHeader: FeeHeader): Promise<StateOverride> {
855
- const { epochDuration, proofSubmissionEpochs } = await this.getRollupConstants();
856
- const roundaboutSize = BigInt(epochDuration * (proofSubmissionEpochs + 1) + 1);
857
- const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
858
-
859
- // tempCheckpointLogs is at offset 2 in RollupStore
860
- const tempCheckpointLogsMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 2n;
935
+ public async makeTempCheckpointLogOverride(
936
+ checkpointNumber: CheckpointNumber,
937
+ fields: TempCheckpointLogOverrideFields,
938
+ ): Promise<StateOverride> {
939
+ const constants = await this.getRollupConstants();
940
+ const slotAt = (field: TempCheckpointLogField) =>
941
+ `0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
942
+ const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
861
943
 
862
- // Solidity mapping slot: keccak256(abi.encode(key, baseSlot))
863
- const structBaseSlot = hexToBigInt(
864
- keccak256(
865
- encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsMappingBase]),
866
- ),
867
- );
944
+ const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
868
945
 
869
- // feeHeader is the 7th field (offset 6) in CompressedTempCheckpointLog
870
- const feeHeaderSlot = structBaseSlot + 6n;
871
- const compressed = RollupContract.compressFeeHeader(feeHeader);
946
+ if (fields.headerHash) {
947
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
948
+ }
949
+ if (fields.outHash) {
950
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
951
+ }
952
+ if (fields.payloadDigest) {
953
+ stateDiff.push({
954
+ slot: slotAt(TempCheckpointLogField.PayloadDigest),
955
+ value: fields.payloadDigest.toString() as `0x${string}`,
956
+ });
957
+ }
958
+ if (fields.slotNumber !== undefined) {
959
+ // CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
960
+ // so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
961
+ const slotNumber = BigInt(fields.slotNumber);
962
+ if (slotNumber < 0n || slotNumber > 0xffffffffn) {
963
+ throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
964
+ }
965
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
966
+ }
967
+ if (fields.feeHeader) {
968
+ stateDiff.push({
969
+ slot: slotAt(TempCheckpointLogField.FeeHeader),
970
+ value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
971
+ });
972
+ }
872
973
 
873
- return [
874
- {
875
- address: this.address,
876
- stateDiff: [
877
- {
878
- slot: `0x${feeHeaderSlot.toString(16).padStart(64, '0')}`,
879
- value: `0x${compressed.toString(16).padStart(64, '0')}`,
880
- },
881
- ],
882
- },
883
- ];
974
+ if (stateDiff.length === 0) {
975
+ return [];
976
+ }
977
+ return [{ address: this.address, stateDiff }];
884
978
  }
885
979
 
886
980
  /**
@@ -1098,13 +1192,20 @@ export class RollupContract {
1098
1192
  }
1099
1193
 
1100
1194
  async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
1101
- const attesterSize = await this.getActiveAttesterCount();
1195
+ // Pin every read to a single L1 block so the attester count and the chunked index reads
1196
+ // observe a consistent set. Without this, the count and each chunk default to `latest` and
1197
+ // can straddle a block boundary (or reorg), yielding an inconsistent or truncated set.
1198
+ const block = await this.client.getBlock();
1199
+ const blockNumber = block.number ?? undefined;
1200
+ const ts = timestamp ?? block.timestamp;
1201
+ const attesterSize = await this.getActiveAttesterCount({ blockNumber });
1102
1202
  const gse = new GSEContract(this.client, await this.getGSE());
1103
- const ts = timestamp ?? (await this.client.getBlock()).timestamp;
1104
1203
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
1105
1204
  const chunks = chunk(indices, 1000);
1106
1205
 
1107
- const results = await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)));
1206
+ const results = await Promise.all(
1207
+ chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, { blockNumber })),
1208
+ );
1108
1209
  return results.flat().map(addr => EthAddress.fromString(addr));
1109
1210
  }
1110
1211
 
@@ -1206,7 +1307,7 @@ export class RollupContract {
1206
1307
  }
1207
1308
 
1208
1309
  public listenToCheckpointInvalidated(
1209
- callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
1310
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
1210
1311
  ): WatchContractEventReturnType {
1211
1312
  return this.rollup.watchEvent.CheckpointInvalidated(
1212
1313
  {},
@@ -1215,7 +1316,7 @@ export class RollupContract {
1215
1316
  for (const log of logs) {
1216
1317
  const args = log.args;
1217
1318
  if (args.checkpointNumber !== undefined) {
1218
- callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
1319
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
1219
1320
  }
1220
1321
  }
1221
1322
  },
@@ -1248,6 +1349,17 @@ export class RollupContract {
1248
1349
  );
1249
1350
  }
1250
1351
 
1352
+ /**
1353
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1354
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1355
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1356
+ * probe to detect RPC nodes that prune historical logs.
1357
+ */
1358
+ async getOwnershipTransferredEventsAtDeploy() {
1359
+ const l1StartBlock = await this.getL1StartBlock();
1360
+ return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
1361
+ }
1362
+
1251
1363
  /** Fetches CheckpointProposed events within the given block range. */
1252
1364
  async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1253
1365
  const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
@@ -1280,4 +1392,48 @@ export class RollupContract {
1280
1392
  },
1281
1393
  }));
1282
1394
  }
1395
+
1396
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1397
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1398
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1399
+ }
1400
+
1401
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1402
+ static get chainTipsStorageSlot(): bigint {
1403
+ return BigInt(RollupContract.stfStorageSlot);
1404
+ }
1405
+
1406
+ /**
1407
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1408
+ * @param checkpointNumber - The checkpoint number
1409
+ * @param field - The field within the CompressedTempCheckpointLog struct
1410
+ */
1411
+ async getTempCheckpointLogStorageSlot(
1412
+ checkpointNumber: CheckpointNumber,
1413
+ field: TempCheckpointLogField,
1414
+ ): Promise<bigint> {
1415
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1416
+ this.getEpochDuration(),
1417
+ this.getProofSubmissionEpochs(),
1418
+ ]);
1419
+ return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
1420
+ }
1421
+
1422
+ private computeTempCheckpointLogStorageSlot(
1423
+ checkpointNumber: CheckpointNumber,
1424
+ field: TempCheckpointLogField,
1425
+ constants: { epochDuration: number; proofSubmissionEpochs: number },
1426
+ ): bigint {
1427
+ const fieldOffset = BigInt(field);
1428
+ const { epochDuration, proofSubmissionEpochs } = constants;
1429
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1430
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1431
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1432
+ const entryBase = BigInt(
1433
+ keccak256(
1434
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1435
+ ),
1436
+ );
1437
+ return entryBase + fieldOffset;
1438
+ }
1283
1439
  }
@@ -7,7 +7,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
7
7
  import { Signature } from '@aztec/foundation/eth-signature';
8
8
  import { hexToBuffer } from '@aztec/foundation/string';
9
9
  import { SlasherAbi } from '@aztec/l1-artifacts/SlasherAbi';
10
- import { TallySlashingProposerAbi } from '@aztec/l1-artifacts/TallySlashingProposerAbi';
10
+ import { SlashingProposerAbi } from '@aztec/l1-artifacts/SlashingProposerAbi';
11
11
 
12
12
  import {
13
13
  type GetContractReturnType,
@@ -19,13 +19,11 @@ import {
19
19
  } from 'viem';
20
20
 
21
21
  /**
22
- * Wrapper around the TallySlashingProposer contract that provides
22
+ * Wrapper around the SlashingProposer contract that provides
23
23
  * a TypeScript interface for interacting with the consensus-based slashing system.
24
24
  */
25
- export class TallySlashingProposerContract {
26
- private readonly contract: GetContractReturnType<typeof TallySlashingProposerAbi, ViemClient>;
27
-
28
- public readonly type = 'tally' as const;
25
+ export class SlashingProposerContract {
26
+ private readonly contract: GetContractReturnType<typeof SlashingProposerAbi, ViemClient>;
29
27
 
30
28
  constructor(
31
29
  public readonly client: ViemClient,
@@ -33,7 +31,7 @@ export class TallySlashingProposerContract {
33
31
  ) {
34
32
  this.contract = getContract({
35
33
  address: typeof address === 'string' ? address : address.toString(),
36
- abi: TallySlashingProposerAbi,
34
+ abi: SlashingProposerAbi,
37
35
  client,
38
36
  });
39
37
  }
@@ -136,12 +134,12 @@ export class TallySlashingProposerContract {
136
134
 
137
135
  /** Tries to extract a VoteCast event from the given logs. */
138
136
  public tryExtractVoteCastEvent(logs: Log[]) {
139
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'VoteCast');
137
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'VoteCast');
140
138
  }
141
139
 
142
140
  /** Tries to extract a RoundExecuted event from the given logs. */
143
141
  public tryExtractRoundExecutedEvent(logs: Log[]) {
144
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'RoundExecuted');
142
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'RoundExecuted');
145
143
  }
146
144
 
147
145
  /**
@@ -161,9 +159,9 @@ export class TallySlashingProposerContract {
161
159
 
162
160
  return {
163
161
  to: this.contract.address,
164
- abi: TallySlashingProposerAbi,
162
+ abi: SlashingProposerAbi,
165
163
  data: encodeFunctionData({
166
- abi: TallySlashingProposerAbi,
164
+ abi: SlashingProposerAbi,
167
165
  functionName: 'vote',
168
166
  args: [votes, signature.toViemSignature()],
169
167
  }),
@@ -173,7 +171,7 @@ export class TallySlashingProposerContract {
173
171
  /** Returns the typed data definition to EIP712-sign for voting */
174
172
  public buildVoteTypedData(votes: Hex, slot: SlotNumber): TypedDataDefinition {
175
173
  const domain = {
176
- name: 'TallySlashingProposer',
174
+ name: 'SlashingProposer',
177
175
  version: '1',
178
176
  chainId: this.client.chain.id,
179
177
  verifyingContract: this.contract.address,
@@ -209,9 +207,9 @@ export class TallySlashingProposerContract {
209
207
  public buildVoteRequestWithSignature(votes: Hex, signature: { v: number; r: Hex; s: Hex }): L1TxRequest {
210
208
  return {
211
209
  to: this.contract.address,
212
- abi: TallySlashingProposerAbi,
210
+ abi: SlashingProposerAbi,
213
211
  data: encodeFunctionData({
214
- abi: TallySlashingProposerAbi,
212
+ abi: SlashingProposerAbi,
215
213
  functionName: 'vote',
216
214
  args: [votes, signature],
217
215
  }),
@@ -227,9 +225,9 @@ export class TallySlashingProposerContract {
227
225
  public buildExecuteRoundRequest(round: bigint, committees: EthAddress[][]): L1TxRequest {
228
226
  return {
229
227
  to: this.contract.address,
230
- abi: mergeAbis([TallySlashingProposerAbi, SlasherAbi]),
228
+ abi: mergeAbis([SlashingProposerAbi, SlasherAbi]),
231
229
  data: encodeFunctionData({
232
- abi: TallySlashingProposerAbi,
230
+ abi: SlashingProposerAbi,
233
231
  functionName: 'executeRound',
234
232
  args: [round, committees.map(c => c.map(addr => addr.toString()))],
235
233
  }),