@aztec/ethereum 0.0.1-commit.d431d1c → 0.0.1-commit.d939eb5aa

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