@aztec/ethereum 0.0.1-commit.d1f2d6c → 0.0.1-commit.d20b825a7

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 (165) 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 +8 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +15 -11
  7. package/dest/contracts/chain_state_override.d.ts +38 -0
  8. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  9. package/dest/contracts/chain_state_override.js +100 -0
  10. package/dest/contracts/empire_base.d.ts +3 -1
  11. package/dest/contracts/empire_base.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_handler.d.ts +1 -1
  13. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  14. package/dest/contracts/fee_asset_handler.js +2 -0
  15. package/dest/contracts/fee_asset_price_oracle.d.ts +101 -0
  16. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  17. package/dest/contracts/fee_asset_price_oracle.js +651 -0
  18. package/dest/contracts/governance.d.ts +1 -1
  19. package/dest/contracts/governance.d.ts.map +1 -1
  20. package/dest/contracts/governance.js +5 -3
  21. package/dest/contracts/governance_proposer.d.ts +3 -1
  22. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  23. package/dest/contracts/governance_proposer.js +13 -1
  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 +11 -6
  27. package/dest/contracts/index.d.ts +4 -3
  28. package/dest/contracts/index.d.ts.map +1 -1
  29. package/dest/contracts/index.js +3 -2
  30. package/dest/contracts/multicall.d.ts +51 -2
  31. package/dest/contracts/multicall.d.ts.map +1 -1
  32. package/dest/contracts/multicall.js +87 -1
  33. package/dest/contracts/registry.d.ts +3 -1
  34. package/dest/contracts/registry.d.ts.map +1 -1
  35. package/dest/contracts/registry.js +30 -1
  36. package/dest/contracts/rollup.d.ts +4985 -25
  37. package/dest/contracts/rollup.d.ts.map +1 -1
  38. package/dest/contracts/rollup.js +320 -61
  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} +18 -13
  42. package/dest/deploy_aztec_l1_contracts.d.ts +4 -8
  43. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  44. package/dest/deploy_aztec_l1_contracts.js +39 -26
  45. package/dest/deploy_l1_contract.js +3 -3
  46. package/dest/generated/l1-contracts-defaults.d.ts +2 -2
  47. package/dest/generated/l1-contracts-defaults.js +2 -2
  48. package/dest/l1_artifacts.d.ts +9384 -15802
  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 +1 -5
  52. package/dest/l1_contract_addresses.d.ts.map +1 -1
  53. package/dest/l1_contract_addresses.js +0 -6
  54. package/dest/l1_reader.d.ts +3 -1
  55. package/dest/l1_reader.d.ts.map +1 -1
  56. package/dest/l1_reader.js +6 -1
  57. package/dest/l1_tx_utils/config.d.ts +7 -1
  58. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  59. package/dest/l1_tx_utils/config.js +14 -1
  60. package/dest/l1_tx_utils/constants.d.ts +1 -1
  61. package/dest/l1_tx_utils/constants.js +2 -2
  62. package/dest/l1_tx_utils/factory.d.ts +18 -10
  63. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  64. package/dest/l1_tx_utils/factory.js +17 -7
  65. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  66. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  67. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  68. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  69. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  70. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  71. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  72. package/dest/l1_tx_utils/index-blobs.js +2 -2
  73. package/dest/l1_tx_utils/index.d.ts +2 -1
  74. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  75. package/dest/l1_tx_utils/index.js +1 -0
  76. package/dest/l1_tx_utils/l1_tx_utils.d.ts +20 -7
  77. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  78. package/dest/l1_tx_utils/l1_tx_utils.js +81 -57
  79. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  80. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  81. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +11 -7
  82. package/dest/l1_tx_utils/tx_delayer.d.ts +56 -0
  83. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  84. package/dest/{test → l1_tx_utils}/tx_delayer.js +62 -34
  85. package/dest/publisher_manager.d.ts +21 -6
  86. package/dest/publisher_manager.d.ts.map +1 -1
  87. package/dest/publisher_manager.js +81 -7
  88. package/dest/queries.d.ts +13 -2
  89. package/dest/queries.d.ts.map +1 -1
  90. package/dest/queries.js +62 -3
  91. package/dest/test/chain_monitor.d.ts +23 -3
  92. package/dest/test/chain_monitor.d.ts.map +1 -1
  93. package/dest/test/chain_monitor.js +38 -2
  94. package/dest/test/eth_cheat_codes.d.ts +6 -4
  95. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  96. package/dest/test/eth_cheat_codes.js +6 -4
  97. package/dest/test/index.d.ts +1 -3
  98. package/dest/test/index.d.ts.map +1 -1
  99. package/dest/test/index.js +0 -2
  100. package/dest/test/rollup_cheat_codes.d.ts +7 -1
  101. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  102. package/dest/test/rollup_cheat_codes.js +18 -1
  103. package/dest/test/start_anvil.d.ts +23 -3
  104. package/dest/test/start_anvil.d.ts.map +1 -1
  105. package/dest/test/start_anvil.js +143 -29
  106. package/dest/test/upgrade_utils.js +2 -2
  107. package/dest/utils.d.ts +2 -1
  108. package/dest/utils.d.ts.map +1 -1
  109. package/dest/utils.js +62 -12
  110. package/package.json +5 -7
  111. package/src/client.ts +28 -2
  112. package/src/config.ts +24 -15
  113. package/src/contracts/chain_state_override.ts +147 -0
  114. package/src/contracts/empire_base.ts +2 -0
  115. package/src/contracts/fee_asset_handler.ts +2 -0
  116. package/src/contracts/fee_asset_price_oracle.ts +280 -0
  117. package/src/contracts/governance.ts +5 -3
  118. package/src/contracts/governance_proposer.ts +10 -1
  119. package/src/contracts/inbox.ts +12 -4
  120. package/src/contracts/index.ts +3 -2
  121. package/src/contracts/multicall.ts +70 -3
  122. package/src/contracts/registry.ts +31 -1
  123. package/src/contracts/rollup.ts +349 -61
  124. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +16 -14
  125. package/src/deploy_aztec_l1_contracts.ts +61 -34
  126. package/src/deploy_l1_contract.ts +3 -3
  127. package/src/generated/l1-contracts-defaults.ts +2 -2
  128. package/src/l1_artifacts.ts +12 -35
  129. package/src/l1_contract_addresses.ts +0 -7
  130. package/src/l1_reader.ts +13 -1
  131. package/src/l1_tx_utils/config.ts +20 -0
  132. package/src/l1_tx_utils/constants.ts +2 -2
  133. package/src/l1_tx_utils/factory.ts +31 -31
  134. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  135. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  136. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  137. package/src/l1_tx_utils/index-blobs.ts +2 -2
  138. package/src/l1_tx_utils/index.ts +1 -0
  139. package/src/l1_tx_utils/l1_tx_utils.ts +74 -36
  140. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +11 -7
  141. package/src/{test → l1_tx_utils}/tx_delayer.ts +78 -50
  142. package/src/publisher_manager.ts +107 -10
  143. package/src/queries.ts +70 -4
  144. package/src/test/chain_monitor.ts +65 -3
  145. package/src/test/eth_cheat_codes.ts +6 -4
  146. package/src/test/index.ts +0 -2
  147. package/src/test/rollup_cheat_codes.ts +20 -1
  148. package/src/test/start_anvil.ts +177 -29
  149. package/src/test/upgrade_utils.ts +2 -2
  150. package/src/utils.ts +70 -14
  151. package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
  152. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  153. package/dest/contracts/empire_slashing_proposer.js +0 -209
  154. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  155. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  156. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  157. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  158. package/dest/test/delayed_tx_utils.d.ts +0 -13
  159. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  160. package/dest/test/delayed_tx_utils.js +0 -28
  161. package/dest/test/tx_delayer.d.ts +0 -36
  162. package/dest/test/tx_delayer.d.ts.map +0 -1
  163. package/src/contracts/empire_slashing_proposer.ts +0 -270
  164. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  165. package/src/test/delayed_tx_utils.ts +0 -52
@@ -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,25 @@ 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
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
144
+ export type ManaMinFeeComponents = {
145
+ sequencerCost: bigint;
146
+ proverCost: bigint;
147
+ congestionCost: bigint;
148
+ congestionMultiplier: bigint;
149
+ };
150
+
137
151
  /**
138
152
  * Reward configuration for the rollup
139
153
  */
@@ -193,10 +207,10 @@ export type CheckpointProposedArgs = {
193
207
  checkpointNumber: CheckpointNumber;
194
208
  archive: Fr;
195
209
  versionedBlobHashes: Buffer[];
196
- /** Hash of attestations. Undefined for older events (backwards compatibility). */
197
- attestationsHash?: Buffer32;
198
- /** Digest of the payload. Undefined for older events (backwards compatibility). */
199
- payloadDigest?: Buffer32;
210
+ /** Hash of attestations emitted in the CheckpointProposed event. */
211
+ attestationsHash: Buffer32;
212
+ /** Digest of the payload emitted in the CheckpointProposed event. */
213
+ payloadDigest: Buffer32;
200
214
  };
201
215
 
202
216
  /** Log type for CheckpointProposed events. */
@@ -204,6 +218,7 @@ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
204
218
 
205
219
  export class RollupContract {
206
220
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
221
+ private readonly logger = createLogger('ethereum:rollup');
207
222
 
208
223
  private static cachedStfStorageSlot: Hex | undefined;
209
224
  private cachedEscapeHatch?: {
@@ -259,34 +274,18 @@ export class RollupContract {
259
274
  return this.rollup;
260
275
  }
261
276
 
262
- public async getSlashingProposer(): Promise<
263
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
264
- > {
277
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
265
278
  const slasher = await this.getSlasherContract();
266
279
  if (!slasher) {
267
280
  return undefined;
268
281
  }
269
282
 
270
283
  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}`);
284
+ if (proposerAddress.isZero()) {
285
+ return undefined;
289
286
  }
287
+
288
+ return new SlashingProposerContract(this.client, proposerAddress);
290
289
  }
291
290
 
292
291
  @memoize
@@ -379,6 +378,20 @@ export class RollupContract {
379
378
  return Fr.fromString(await this.rollup.read.archiveAt([0n]));
380
379
  }
381
380
 
381
+ @memoize
382
+ async getVkTreeRoot(): Promise<Fr> {
383
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
384
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
385
+ return Fr.fromString(value ?? '0x0');
386
+ }
387
+
388
+ @memoize
389
+ async getProtocolContractsHash(): Promise<Fr> {
390
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
391
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
392
+ return Fr.fromString(value ?? '0x0');
393
+ }
394
+
382
395
  /**
383
396
  * Returns rollup constants used for epoch queries.
384
397
  * Return type is `L1RollupConstants` which is defined in stdlib,
@@ -391,13 +404,25 @@ export class RollupContract {
391
404
  slotDuration: number;
392
405
  epochDuration: number;
393
406
  proofSubmissionEpochs: number;
407
+ targetCommitteeSize: number;
408
+ rollupManaLimit: number;
394
409
  }> {
395
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
410
+ const [
411
+ l1StartBlock,
412
+ l1GenesisTime,
413
+ slotDuration,
414
+ epochDuration,
415
+ proofSubmissionEpochs,
416
+ targetCommitteeSize,
417
+ rollupManaLimit,
418
+ ] = await Promise.all([
396
419
  this.getL1StartBlock(),
397
420
  this.getL1GenesisTime(),
398
421
  this.getSlotDuration(),
399
422
  this.getEpochDuration(),
400
423
  this.getProofSubmissionEpochs(),
424
+ this.getTargetCommitteeSize(),
425
+ this.getManaLimit(),
401
426
  ]);
402
427
  return {
403
428
  l1StartBlock,
@@ -405,6 +430,8 @@ export class RollupContract {
405
430
  slotDuration,
406
431
  epochDuration: Number(epochDuration),
407
432
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
433
+ targetCommitteeSize,
434
+ rollupManaLimit: Number(rollupManaLimit),
408
435
  };
409
436
  }
410
437
 
@@ -459,7 +486,11 @@ export class RollupContract {
459
486
 
460
487
  const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
461
488
  return isOpen;
462
- } catch {
489
+ } catch (err) {
490
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
491
+ epoch: Number(epoch),
492
+ error: err,
493
+ });
463
494
  return false;
464
495
  }
465
496
  }
@@ -495,26 +526,41 @@ export class RollupContract {
495
526
  return this.rollup.read.getCheckpointReward();
496
527
  }
497
528
 
498
- async getCheckpointNumber(): Promise<CheckpointNumber> {
499
- return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
529
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
530
+ await checkBlockTag(options?.blockNumber, this.client);
531
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
500
532
  }
501
533
 
502
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
503
- return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber());
534
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
535
+ await checkBlockTag(options?.blockNumber, this.client);
536
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
504
537
  }
505
538
 
506
- async getSlotNumber(): Promise<SlotNumber> {
507
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
539
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
540
+ await checkBlockTag(options?.blockNumber, this.client);
541
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
508
542
  }
509
543
 
510
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
511
- const result = await this.rollup.read.getL1FeesAt([timestamp]);
544
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
545
+ await checkBlockTag(options?.blockNumber, this.client);
546
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
512
547
  return {
513
548
  baseFee: result.baseFee,
514
549
  blobFee: result.blobFee,
515
550
  };
516
551
  }
517
552
 
553
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
554
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
555
+ return {
556
+ excessMana: result.excessMana,
557
+ manaUsed: result.manaUsed,
558
+ ethPerFeeAsset: result.ethPerFeeAsset,
559
+ congestionCost: result.congestionCost,
560
+ proverCost: result.proverCost,
561
+ };
562
+ }
563
+
518
564
  getEthPerFeeAsset(): Promise<bigint> {
519
565
  return this.rollup.read.getEthPerFeeAsset();
520
566
  }
@@ -589,8 +635,9 @@ export class RollupContract {
589
635
  return EthAddress.fromString(result);
590
636
  }
591
637
 
592
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
593
- const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)]);
638
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
639
+ await checkBlockTag(options?.blockNumber, this.client);
640
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
594
641
  return {
595
642
  archive: Fr.fromString(result.archive),
596
643
  headerHash: Buffer32.fromString(result.headerHash),
@@ -623,6 +670,30 @@ export class RollupContract {
623
670
  );
624
671
  }
625
672
 
673
+ /**
674
+ * Returns the effective pending checkpoint, accounting for potential prunes.
675
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
676
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
677
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
678
+ * @param options - Optional L1 block number to pin the queries to.
679
+ */
680
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
681
+ return retry(
682
+ async () => {
683
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
684
+ const canPrune = await this.canPruneAtTime(timestamp, options);
685
+ if (canPrune) {
686
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
687
+ return await this.getCheckpoint(provenCheckpointNumber, options);
688
+ }
689
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
690
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
691
+ },
692
+ 'getting effective pending checkpoint',
693
+ makeBackoff([0.5, 0.5, 0.5]),
694
+ );
695
+ }
696
+
626
697
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
627
698
  const { pending, proven } = await this.rollup.read.getTips();
628
699
  return {
@@ -741,14 +812,13 @@ export class RollupContract {
741
812
  * timestamp of the next L1 block
742
813
  * @throws otherwise
743
814
  */
744
- public async canProposeAtNextEthBlock(
815
+ public async canProposeAt(
745
816
  archive: Buffer,
746
817
  account: `0x${string}` | Account,
747
- slotDuration: number,
748
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
818
+ timestamp: bigint,
819
+ stateOverride: StateOverride = [],
749
820
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
750
- const latestBlock = await this.client.getBlock();
751
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
821
+ const timeOfNextL1Slot = timestamp;
752
822
  const who = typeof account === 'string' ? account : account.address;
753
823
 
754
824
  try {
@@ -760,7 +830,7 @@ export class RollupContract {
760
830
  functionName: 'canProposeAtTime',
761
831
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
762
832
  account,
763
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
833
+ stateOverride,
764
834
  });
765
835
 
766
836
  return {
@@ -796,6 +866,151 @@ export class RollupContract {
796
866
  ];
797
867
  }
798
868
 
869
+ /**
870
+ * Returns a state override that sets tempCheckpointLogs[checkpointNumber].feeHeader to the compressed fee header.
871
+ * Used when simulating a propose call where the parent checkpoint hasn't landed on L1 yet (pipelining).
872
+ */
873
+ public async makeFeeHeaderOverride(checkpointNumber: CheckpointNumber, feeHeader: FeeHeader): Promise<StateOverride> {
874
+ const { epochDuration, proofSubmissionEpochs } = await this.getRollupConstants();
875
+ const roundaboutSize = BigInt(epochDuration * (proofSubmissionEpochs + 1) + 1);
876
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
877
+
878
+ // tempCheckpointLogs is at offset 2 in RollupStore
879
+ const tempCheckpointLogsMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 2n;
880
+
881
+ // Solidity mapping slot: keccak256(abi.encode(key, baseSlot))
882
+ const structBaseSlot = hexToBigInt(
883
+ keccak256(
884
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsMappingBase]),
885
+ ),
886
+ );
887
+
888
+ // feeHeader is the 7th field (offset 6) in CompressedTempCheckpointLog
889
+ const feeHeaderSlot = structBaseSlot + 6n;
890
+ const compressed = RollupContract.compressFeeHeader(feeHeader);
891
+
892
+ return [
893
+ {
894
+ address: this.address,
895
+ stateDiff: [
896
+ {
897
+ slot: `0x${feeHeaderSlot.toString(16).padStart(64, '0')}`,
898
+ value: `0x${compressed.toString(16).padStart(64, '0')}`,
899
+ },
900
+ ],
901
+ },
902
+ ];
903
+ }
904
+
905
+ /**
906
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
907
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
908
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
909
+ */
910
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
911
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
912
+ const archiveSlot = hexToBigInt(
913
+ keccak256(
914
+ encodeAbiParameters(
915
+ [{ type: 'uint256' }, { type: 'uint256' }],
916
+ [BigInt(checkpointNumber), archivesMappingBase],
917
+ ),
918
+ ),
919
+ );
920
+ return [
921
+ {
922
+ address: this.address,
923
+ stateDiff: [
924
+ {
925
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
926
+ value: archive.toString(),
927
+ },
928
+ ],
929
+ },
930
+ ];
931
+ }
932
+
933
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
934
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
935
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
936
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
937
+ for (const override of overrides) {
938
+ for (const entry of override) {
939
+ const key = entry.address.toLowerCase();
940
+ const existing = byAddress.get(key);
941
+ if (existing) {
942
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
943
+ if (entry.balance !== undefined) {
944
+ existing.balance = entry.balance;
945
+ }
946
+ } else {
947
+ byAddress.set(key, {
948
+ address: entry.address,
949
+ balance: entry.balance,
950
+ stateDiff: [...(entry.stateDiff ?? [])],
951
+ });
952
+ }
953
+ }
954
+ }
955
+ return [...byAddress.values()];
956
+ }
957
+
958
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
959
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
960
+ const MASK_48_BITS = (1n << 48n) - 1n;
961
+ const MASK_64_BITS = (1n << 64n) - 1n;
962
+ const MASK_63_BITS = (1n << 63n) - 1n;
963
+
964
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
965
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
966
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
967
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
968
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
969
+ value |= 1n << 255n; // preheat flag
970
+ return value;
971
+ }
972
+
973
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
974
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
975
+ public static computeChildFeeHeader(
976
+ parentFeeHeader: FeeHeader,
977
+ childManaUsed: bigint,
978
+ feeAssetPriceModifier: bigint,
979
+ manaTarget: bigint,
980
+ ): FeeHeader {
981
+ const MIN_ETH_PER_FEE_ASSET = 100n;
982
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
983
+
984
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
985
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
986
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
987
+
988
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
989
+ const parentPrice =
990
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
991
+ let newPrice: bigint;
992
+ if (feeAssetPriceModifier >= 0n) {
993
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
994
+ } else {
995
+ const absMod = -feeAssetPriceModifier;
996
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
997
+ }
998
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
999
+ newPrice = MIN_ETH_PER_FEE_ASSET;
1000
+ }
1001
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1002
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1003
+ }
1004
+
1005
+ return {
1006
+ excessMana,
1007
+ manaUsed: childManaUsed,
1008
+ ethPerFeeAsset: newPrice,
1009
+ congestionCost: 0n,
1010
+ proverCost: 0n,
1011
+ };
1012
+ }
1013
+
799
1014
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
800
1015
  public buildInvalidateBadAttestationRequest(
801
1016
  checkpointNumber: CheckpointNumber,
@@ -805,6 +1020,7 @@ export class RollupContract {
805
1020
  ): L1TxRequest {
806
1021
  return {
807
1022
  to: this.address,
1023
+ abi: RollupAbi,
808
1024
  data: encodeFunctionData({
809
1025
  abi: RollupAbi,
810
1026
  functionName: 'invalidateBadAttestation',
@@ -826,6 +1042,7 @@ export class RollupContract {
826
1042
  ): L1TxRequest {
827
1043
  return {
828
1044
  to: this.address,
1045
+ abi: RollupAbi,
829
1046
  data: encodeFunctionData({
830
1047
  abi: RollupAbi,
831
1048
  functionName: 'invalidateInsufficientAttestations',
@@ -842,8 +1059,18 @@ export class RollupContract {
842
1059
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
843
1060
  }
844
1061
 
845
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
846
- return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
1062
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1063
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
1064
+ }
1065
+
1066
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1067
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1068
+ return {
1069
+ sequencerCost: result.sequencerCost,
1070
+ proverCost: result.proverCost,
1071
+ congestionCost: result.congestionCost,
1072
+ congestionMultiplier: result.congestionMultiplier,
1073
+ };
847
1074
  }
848
1075
 
849
1076
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -889,11 +1116,10 @@ export class RollupContract {
889
1116
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
890
1117
  }
891
1118
 
892
- async getAttesters(): Promise<EthAddress[]> {
1119
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
893
1120
  const attesterSize = await this.getActiveAttesterCount();
894
1121
  const gse = new GSEContract(this.client, await this.getGSE());
895
- const ts = (await this.client.getBlock()).timestamp;
896
-
1122
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
897
1123
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
898
1124
  const chunks = chunk(indices, 1000);
899
1125
 
@@ -959,6 +1185,7 @@ export class RollupContract {
959
1185
  setupEpoch(l1TxUtils: L1TxUtils) {
960
1186
  return l1TxUtils.sendAndMonitorTransaction({
961
1187
  to: this.address,
1188
+ abi: RollupAbi,
962
1189
  data: encodeFunctionData({
963
1190
  abi: RollupAbi,
964
1191
  functionName: 'setupEpoch',
@@ -970,6 +1197,7 @@ export class RollupContract {
970
1197
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
971
1198
  return l1TxUtils.sendAndMonitorTransaction({
972
1199
  to: this.address,
1200
+ abi: RollupAbi,
973
1201
  data: encodeFunctionData({
974
1202
  abi: RollupAbi,
975
1203
  functionName: 'vote',
@@ -997,7 +1225,7 @@ export class RollupContract {
997
1225
  }
998
1226
 
999
1227
  public listenToCheckpointInvalidated(
1000
- callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
1228
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
1001
1229
  ): WatchContractEventReturnType {
1002
1230
  return this.rollup.watchEvent.CheckpointInvalidated(
1003
1231
  {},
@@ -1006,7 +1234,7 @@ export class RollupContract {
1006
1234
  for (const log of logs) {
1007
1235
  const args = log.args;
1008
1236
  if (args.checkpointNumber !== undefined) {
1009
- callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
1237
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
1010
1238
  }
1011
1239
  }
1012
1240
  },
@@ -1039,6 +1267,17 @@ export class RollupContract {
1039
1267
  );
1040
1268
  }
1041
1269
 
1270
+ /**
1271
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1272
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1273
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1274
+ * probe to detect RPC nodes that prune historical logs.
1275
+ */
1276
+ async getOwnershipTransferredEventsAtDeploy() {
1277
+ const l1StartBlock = await this.getL1StartBlock();
1278
+ return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
1279
+ }
1280
+
1042
1281
  /** Fetches CheckpointProposed events within the given block range. */
1043
1282
  async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1044
1283
  const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
@@ -1052,9 +1291,58 @@ export class RollupContract {
1052
1291
  checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1053
1292
  archive: Fr.fromString(log.args.archive!),
1054
1293
  versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1055
- attestationsHash: log.args.attestationsHash ? Buffer32.fromString(log.args.attestationsHash) : undefined,
1056
- payloadDigest: log.args.payloadDigest ? Buffer32.fromString(log.args.payloadDigest) : undefined,
1294
+ attestationsHash: (() => {
1295
+ if (!log.args.attestationsHash) {
1296
+ throw new Error(
1297
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1298
+ );
1299
+ }
1300
+ return Buffer32.fromString(log.args.attestationsHash);
1301
+ })(),
1302
+ payloadDigest: (() => {
1303
+ if (!log.args.payloadDigest) {
1304
+ throw new Error(
1305
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1306
+ );
1307
+ }
1308
+ return Buffer32.fromString(log.args.payloadDigest);
1309
+ })(),
1057
1310
  },
1058
1311
  }));
1059
1312
  }
1313
+
1314
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1315
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1316
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1317
+ }
1318
+
1319
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1320
+ static get chainTipsStorageSlot(): bigint {
1321
+ return BigInt(RollupContract.stfStorageSlot);
1322
+ }
1323
+
1324
+ /**
1325
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1326
+ * @param checkpointNumber - The checkpoint number
1327
+ * @param field - The field within the CompressedTempCheckpointLog struct
1328
+ */
1329
+ async getTempCheckpointLogStorageSlot(
1330
+ checkpointNumber: CheckpointNumber,
1331
+ field: TempCheckpointLogField,
1332
+ ): Promise<bigint> {
1333
+ const fieldOffset = BigInt(field);
1334
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1335
+ this.getEpochDuration(),
1336
+ this.getProofSubmissionEpochs(),
1337
+ ]);
1338
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1339
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1340
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1341
+ const entryBase = BigInt(
1342
+ keccak256(
1343
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1344
+ ),
1345
+ );
1346
+ return entryBase + fieldOffset;
1347
+ }
1060
1348
  }