@aztec/ethereum 0.0.1-commit.1142ef1 → 0.0.1-commit.11bf3dd6e

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 (171) 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 +22 -33
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +59 -64
  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 +18 -1
  25. package/dest/contracts/inbox.d.ts.map +1 -1
  26. package/dest/contracts/inbox.js +31 -1
  27. package/dest/contracts/index.d.ts +5 -3
  28. package/dest/contracts/index.d.ts.map +1 -1
  29. package/dest/contracts/index.js +4 -2
  30. package/dest/contracts/log.d.ts +13 -0
  31. package/dest/contracts/log.d.ts.map +1 -0
  32. package/dest/contracts/log.js +1 -0
  33. package/dest/contracts/multicall.d.ts +51 -2
  34. package/dest/contracts/multicall.d.ts.map +1 -1
  35. package/dest/contracts/multicall.js +87 -1
  36. package/dest/contracts/registry.d.ts +3 -1
  37. package/dest/contracts/registry.d.ts.map +1 -1
  38. package/dest/contracts/registry.js +30 -1
  39. package/dest/contracts/rollup.d.ts +117 -22
  40. package/dest/contracts/rollup.d.ts.map +1 -1
  41. package/dest/contracts/rollup.js +370 -60
  42. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
  43. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  44. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +18 -13
  45. package/dest/deploy_aztec_l1_contracts.d.ts +9 -8
  46. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  47. package/dest/deploy_aztec_l1_contracts.js +58 -32
  48. package/dest/deploy_l1_contract.js +3 -3
  49. package/dest/generated/l1-contracts-defaults.d.ts +30 -0
  50. package/dest/generated/l1-contracts-defaults.d.ts.map +1 -0
  51. package/dest/generated/l1-contracts-defaults.js +30 -0
  52. package/dest/l1_artifacts.d.ts +10713 -16120
  53. package/dest/l1_artifacts.d.ts.map +1 -1
  54. package/dest/l1_artifacts.js +9 -24
  55. package/dest/l1_contract_addresses.d.ts +1 -5
  56. package/dest/l1_contract_addresses.d.ts.map +1 -1
  57. package/dest/l1_contract_addresses.js +0 -6
  58. package/dest/l1_reader.d.ts +3 -1
  59. package/dest/l1_reader.d.ts.map +1 -1
  60. package/dest/l1_reader.js +6 -1
  61. package/dest/l1_tx_utils/config.d.ts +7 -1
  62. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  63. package/dest/l1_tx_utils/config.js +14 -1
  64. package/dest/l1_tx_utils/constants.d.ts +1 -1
  65. package/dest/l1_tx_utils/constants.js +2 -2
  66. package/dest/l1_tx_utils/factory.d.ts +18 -10
  67. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  68. package/dest/l1_tx_utils/factory.js +17 -7
  69. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  70. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  71. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  72. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  73. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  74. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  75. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  76. package/dest/l1_tx_utils/index-blobs.js +2 -2
  77. package/dest/l1_tx_utils/index.d.ts +2 -1
  78. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  79. package/dest/l1_tx_utils/index.js +1 -0
  80. package/dest/l1_tx_utils/l1_tx_utils.d.ts +20 -7
  81. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  82. package/dest/l1_tx_utils/l1_tx_utils.js +81 -57
  83. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  84. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  85. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +11 -7
  86. package/dest/l1_tx_utils/tx_delayer.d.ts +56 -0
  87. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  88. package/dest/{test → l1_tx_utils}/tx_delayer.js +62 -34
  89. package/dest/publisher_manager.d.ts +21 -6
  90. package/dest/publisher_manager.d.ts.map +1 -1
  91. package/dest/publisher_manager.js +81 -7
  92. package/dest/queries.d.ts +2 -2
  93. package/dest/queries.d.ts.map +1 -1
  94. package/dest/queries.js +7 -4
  95. package/dest/test/chain_monitor.d.ts +22 -3
  96. package/dest/test/chain_monitor.d.ts.map +1 -1
  97. package/dest/test/chain_monitor.js +33 -2
  98. package/dest/test/eth_cheat_codes.d.ts +18 -4
  99. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  100. package/dest/test/eth_cheat_codes.js +6 -4
  101. package/dest/test/index.d.ts +1 -3
  102. package/dest/test/index.d.ts.map +1 -1
  103. package/dest/test/index.js +0 -2
  104. package/dest/test/rollup_cheat_codes.d.ts +8 -2
  105. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  106. package/dest/test/rollup_cheat_codes.js +19 -2
  107. package/dest/test/start_anvil.d.ts +23 -3
  108. package/dest/test/start_anvil.d.ts.map +1 -1
  109. package/dest/test/start_anvil.js +143 -29
  110. package/dest/test/upgrade_utils.js +2 -2
  111. package/dest/utils.d.ts +2 -1
  112. package/dest/utils.d.ts.map +1 -1
  113. package/dest/utils.js +62 -12
  114. package/package.json +8 -9
  115. package/src/client.ts +10 -2
  116. package/src/config.ts +75 -65
  117. package/src/contracts/README.md +157 -0
  118. package/src/contracts/chain_state_override.ts +147 -0
  119. package/src/contracts/empire_base.ts +2 -0
  120. package/src/contracts/fee_asset_handler.ts +2 -0
  121. package/src/contracts/fee_asset_price_oracle.ts +280 -0
  122. package/src/contracts/governance.ts +13 -4
  123. package/src/contracts/governance_proposer.ts +10 -1
  124. package/src/contracts/inbox.ts +48 -1
  125. package/src/contracts/index.ts +4 -2
  126. package/src/contracts/log.ts +13 -0
  127. package/src/contracts/multicall.ts +70 -3
  128. package/src/contracts/registry.ts +31 -1
  129. package/src/contracts/rollup.ts +424 -56
  130. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +16 -14
  131. package/src/deploy_aztec_l1_contracts.ts +81 -40
  132. package/src/deploy_l1_contract.ts +3 -3
  133. package/src/generated/l1-contracts-defaults.ts +32 -0
  134. package/src/l1_artifacts.ts +12 -35
  135. package/src/l1_contract_addresses.ts +0 -7
  136. package/src/l1_reader.ts +13 -1
  137. package/src/l1_tx_utils/config.ts +20 -0
  138. package/src/l1_tx_utils/constants.ts +2 -2
  139. package/src/l1_tx_utils/factory.ts +31 -31
  140. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  141. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  142. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  143. package/src/l1_tx_utils/index-blobs.ts +2 -2
  144. package/src/l1_tx_utils/index.ts +1 -0
  145. package/src/l1_tx_utils/l1_tx_utils.ts +74 -36
  146. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +11 -7
  147. package/src/{test → l1_tx_utils}/tx_delayer.ts +78 -50
  148. package/src/publisher_manager.ts +107 -10
  149. package/src/queries.ts +6 -4
  150. package/src/test/chain_monitor.ts +60 -3
  151. package/src/test/eth_cheat_codes.ts +6 -4
  152. package/src/test/index.ts +0 -2
  153. package/src/test/rollup_cheat_codes.ts +22 -3
  154. package/src/test/start_anvil.ts +177 -29
  155. package/src/test/upgrade_utils.ts +2 -2
  156. package/src/utils.ts +70 -14
  157. package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
  158. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  159. package/dest/contracts/empire_slashing_proposer.js +0 -209
  160. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  161. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  162. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  163. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  164. package/dest/test/delayed_tx_utils.d.ts +0 -13
  165. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  166. package/dest/test/delayed_tx_utils.js +0 -28
  167. package/dest/test/tx_delayer.d.ts +0 -36
  168. package/dest/test/tx_delayer.d.ts.map +0 -1
  169. package/src/contracts/empire_slashing_proposer.ts +0 -270
  170. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  171. package/src/test/delayed_tx_utils.ts +0 -52
@@ -4,7 +4,9 @@ import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { memoize } from '@aztec/foundation/decorators';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
+ import { createLogger } from '@aztec/foundation/log';
7
8
  import { makeBackoff, retry } from '@aztec/foundation/retry';
9
+ import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
8
10
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
9
11
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
10
12
 
@@ -15,6 +17,7 @@ import {
15
17
  type Hex,
16
18
  type StateOverride,
17
19
  type WatchContractEventReturnType,
20
+ encodeAbiParameters,
18
21
  encodeFunctionData,
19
22
  getContract,
20
23
  hexToBigInt,
@@ -28,10 +31,10 @@ import type { L1ReaderConfig } from '../l1_reader.js';
28
31
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
29
32
  import type { ViemClient } from '../types.js';
30
33
  import { formatViemError } from '../utils.js';
31
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
32
34
  import { GSEContract } from './gse.js';
35
+ import type { L1EventLog } from './log.js';
33
36
  import { SlasherContract } from './slasher_contract.js';
34
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
37
+ import { SlashingProposerContract } from './slashing_proposer.js';
35
38
  import { checkBlockTag } from './utils.js';
36
39
 
37
40
  export type ViemCommitteeAttestation = {
@@ -53,7 +56,6 @@ export type L1RollupContractAddresses = Pick<
53
56
  | 'feeJuiceAddress'
54
57
  | 'stakingAssetAddress'
55
58
  | 'rewardDistributorAddress'
56
- | 'slashFactoryAddress'
57
59
  | 'gseAddress'
58
60
  >;
59
61
 
@@ -69,6 +71,7 @@ export type ViemHeader = {
69
71
  blockHeadersHash: `0x${string}`;
70
72
  blobsHash: `0x${string}`;
71
73
  inHash: `0x${string}`;
74
+ outHash: `0x${string}`;
72
75
  slotNumber: bigint;
73
76
  timestamp: bigint;
74
77
  coinbase: `0x${string}`;
@@ -82,12 +85,6 @@ export type ViemGasFees = {
82
85
  feePerL2Gas: bigint;
83
86
  };
84
87
 
85
- export enum SlashingProposerType {
86
- None = 0,
87
- Tally = 1,
88
- Empire = 2,
89
- }
90
-
91
88
  /**
92
89
  * Status of a validator/attester in the staking system.
93
90
  * Matches the Status enum in StakingLib.sol
@@ -105,7 +102,7 @@ export enum AttesterStatus {
105
102
  export type FeeHeader = {
106
103
  excessMana: bigint;
107
104
  manaUsed: bigint;
108
- feeAssetPriceNumerator: bigint;
105
+ ethPerFeeAsset: bigint;
109
106
  congestionCost: bigint;
110
107
  proverCost: bigint;
111
108
  };
@@ -131,6 +128,25 @@ export type L1FeeData = {
131
128
  blobFee: bigint;
132
129
  };
133
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
+
134
150
  /**
135
151
  * Reward configuration for the rollup
136
152
  */
@@ -185,10 +201,29 @@ export type RollupStatusResponse = {
185
201
  archiveOfMyCheckpoint: Fr;
186
202
  };
187
203
 
204
+ /** Arguments for the CheckpointProposed event. */
205
+ export type CheckpointProposedArgs = {
206
+ checkpointNumber: CheckpointNumber;
207
+ archive: Fr;
208
+ versionedBlobHashes: Buffer[];
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;
213
+ };
214
+
215
+ /** Log type for CheckpointProposed events. */
216
+ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
217
+
188
218
  export class RollupContract {
189
219
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
220
+ private readonly logger = createLogger('ethereum:rollup');
190
221
 
191
222
  private static cachedStfStorageSlot: Hex | undefined;
223
+ private cachedEscapeHatch?: {
224
+ address: EthAddress;
225
+ contract: GetContractReturnType<typeof EscapeHatchAbi, ViemClient>;
226
+ };
192
227
 
193
228
  static get checkBlobStorageSlot(): bigint {
194
229
  const asString = RollupStorage.find(storage => storage.label === 'checkBlob')?.slot;
@@ -238,34 +273,18 @@ export class RollupContract {
238
273
  return this.rollup;
239
274
  }
240
275
 
241
- public async getSlashingProposer(): Promise<
242
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
243
- > {
276
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
244
277
  const slasher = await this.getSlasherContract();
245
278
  if (!slasher) {
246
279
  return undefined;
247
280
  }
248
281
 
249
282
  const proposerAddress = await slasher.getProposer();
250
- const proposerAbi = [
251
- {
252
- type: 'function',
253
- name: 'SLASHING_PROPOSER_TYPE',
254
- inputs: [],
255
- outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
256
- stateMutability: 'view',
257
- },
258
- ] as const;
259
-
260
- const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
261
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
262
- if (proposerType === SlashingProposerType.Tally.valueOf()) {
263
- return new TallySlashingProposerContract(this.client, proposerAddress);
264
- } else if (proposerType === SlashingProposerType.Empire.valueOf()) {
265
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
266
- } else {
267
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
283
+ if (proposerAddress.isZero()) {
284
+ return undefined;
268
285
  }
286
+
287
+ return new SlashingProposerContract(this.client, proposerAddress);
269
288
  }
270
289
 
271
290
  @memoize
@@ -358,6 +377,20 @@ export class RollupContract {
358
377
  return Fr.fromString(await this.rollup.read.archiveAt([0n]));
359
378
  }
360
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
+
361
394
  /**
362
395
  * Returns rollup constants used for epoch queries.
363
396
  * Return type is `L1RollupConstants` which is defined in stdlib,
@@ -370,13 +403,25 @@ export class RollupContract {
370
403
  slotDuration: number;
371
404
  epochDuration: number;
372
405
  proofSubmissionEpochs: number;
406
+ targetCommitteeSize: number;
407
+ rollupManaLimit: number;
373
408
  }> {
374
- 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([
375
418
  this.getL1StartBlock(),
376
419
  this.getL1GenesisTime(),
377
420
  this.getSlotDuration(),
378
421
  this.getEpochDuration(),
379
422
  this.getProofSubmissionEpochs(),
423
+ this.getTargetCommitteeSize(),
424
+ this.getManaLimit(),
380
425
  ]);
381
426
  return {
382
427
  l1StartBlock,
@@ -384,6 +429,8 @@ export class RollupContract {
384
429
  slotDuration,
385
430
  epochDuration: Number(epochDuration),
386
431
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
432
+ targetCommitteeSize,
433
+ rollupManaLimit: Number(rollupManaLimit),
387
434
  };
388
435
  }
389
436
 
@@ -391,6 +438,62 @@ export class RollupContract {
391
438
  return EthAddress.fromString(await this.rollup.read.getSlasher());
392
439
  }
393
440
 
441
+ /**
442
+ * Returns the configured escape hatch contract address, or zero if disabled.
443
+ */
444
+ async getEscapeHatchAddress(): Promise<EthAddress> {
445
+ return EthAddress.fromString(await this.rollup.read.getEscapeHatch());
446
+ }
447
+
448
+ private async getEscapeHatchContract(): Promise<
449
+ GetContractReturnType<typeof EscapeHatchAbi, ViemClient> | undefined
450
+ > {
451
+ const escapeHatchAddress = await this.getEscapeHatchAddress();
452
+ if (escapeHatchAddress.isZero()) {
453
+ return undefined;
454
+ }
455
+
456
+ // Cache the viem contract wrapper since it will be used frequently.
457
+ if (!this.cachedEscapeHatch || !this.cachedEscapeHatch.address.equals(escapeHatchAddress)) {
458
+ this.cachedEscapeHatch = {
459
+ address: escapeHatchAddress,
460
+ contract: getContract({
461
+ address: escapeHatchAddress.toString(),
462
+ abi: EscapeHatchAbi,
463
+ client: this.client,
464
+ }),
465
+ };
466
+ }
467
+
468
+ return this.cachedEscapeHatch.contract;
469
+ }
470
+
471
+ /**
472
+ * Returns whether the escape hatch is open for the given epoch.
473
+ * If escape hatch is not configured, returns false.
474
+ *
475
+ * This function is intentionally defensive: any failure to query the escape hatch
476
+ * (RPC issues, transient errors, etc.) is treated as "closed" to avoid callers
477
+ * needing to sprinkle try/catch everywhere.
478
+ */
479
+ async isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean> {
480
+ try {
481
+ const escapeHatch = await this.getEscapeHatchContract();
482
+ if (!escapeHatch) {
483
+ return false;
484
+ }
485
+
486
+ const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
487
+ return isOpen;
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
+ });
493
+ return false;
494
+ }
495
+ }
496
+
394
497
  /**
395
498
  * Returns a SlasherContract instance for interacting with the slasher contract.
396
499
  */
@@ -422,28 +525,43 @@ export class RollupContract {
422
525
  return this.rollup.read.getCheckpointReward();
423
526
  }
424
527
 
425
- async getCheckpointNumber(): Promise<CheckpointNumber> {
426
- 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));
427
531
  }
428
532
 
429
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
430
- 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));
431
536
  }
432
537
 
433
- async getSlotNumber(): Promise<SlotNumber> {
434
- 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));
435
541
  }
436
542
 
437
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
438
- 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);
439
546
  return {
440
547
  baseFee: result.baseFee,
441
548
  blobFee: result.blobFee,
442
549
  };
443
550
  }
444
551
 
445
- getFeeAssetPerEth(): Promise<bigint> {
446
- 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();
447
565
  }
448
566
 
449
567
  async getCommitteeAt(timestamp: bigint): Promise<EthAddress[] | undefined> {
@@ -516,8 +634,9 @@ export class RollupContract {
516
634
  return EthAddress.fromString(result);
517
635
  }
518
636
 
519
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
520
- 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);
521
640
  return {
522
641
  archive: Fr.fromString(result.archive),
523
642
  headerHash: Buffer32.fromString(result.headerHash),
@@ -528,7 +647,7 @@ export class RollupContract {
528
647
  feeHeader: {
529
648
  excessMana: result.feeHeader.excessMana,
530
649
  manaUsed: result.feeHeader.manaUsed,
531
- feeAssetPriceNumerator: result.feeHeader.feeAssetPriceNumerator,
650
+ ethPerFeeAsset: result.feeHeader.ethPerFeeAsset,
532
651
  congestionCost: result.feeHeader.congestionCost,
533
652
  proverCost: result.feeHeader.proverCost,
534
653
  },
@@ -550,6 +669,30 @@ export class RollupContract {
550
669
  );
551
670
  }
552
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
+
553
696
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
554
697
  const { pending, proven } = await this.rollup.read.getTips();
555
698
  return {
@@ -668,14 +811,13 @@ export class RollupContract {
668
811
  * timestamp of the next L1 block
669
812
  * @throws otherwise
670
813
  */
671
- public async canProposeAtNextEthBlock(
814
+ public async canProposeAt(
672
815
  archive: Buffer,
673
816
  account: `0x${string}` | Account,
674
- slotDuration: number,
675
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
817
+ timestamp: bigint,
818
+ stateOverride: StateOverride = [],
676
819
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
677
- const latestBlock = await this.client.getBlock();
678
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
820
+ const timeOfNextL1Slot = timestamp;
679
821
  const who = typeof account === 'string' ? account : account.address;
680
822
 
681
823
  try {
@@ -687,7 +829,7 @@ export class RollupContract {
687
829
  functionName: 'canProposeAtTime',
688
830
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
689
831
  account,
690
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
832
+ stateOverride,
691
833
  });
692
834
 
693
835
  return {
@@ -723,6 +865,151 @@ export class RollupContract {
723
865
  ];
724
866
  }
725
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
+
726
1013
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
727
1014
  public buildInvalidateBadAttestationRequest(
728
1015
  checkpointNumber: CheckpointNumber,
@@ -732,6 +1019,7 @@ export class RollupContract {
732
1019
  ): L1TxRequest {
733
1020
  return {
734
1021
  to: this.address,
1022
+ abi: RollupAbi,
735
1023
  data: encodeFunctionData({
736
1024
  abi: RollupAbi,
737
1025
  functionName: 'invalidateBadAttestation',
@@ -753,6 +1041,7 @@ export class RollupContract {
753
1041
  ): L1TxRequest {
754
1042
  return {
755
1043
  to: this.address,
1044
+ abi: RollupAbi,
756
1045
  data: encodeFunctionData({
757
1046
  abi: RollupAbi,
758
1047
  functionName: 'invalidateInsufficientAttestations',
@@ -769,8 +1058,18 @@ export class RollupContract {
769
1058
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
770
1059
  }
771
1060
 
772
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
773
- 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
+ };
774
1073
  }
775
1074
 
776
1075
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -816,11 +1115,10 @@ export class RollupContract {
816
1115
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
817
1116
  }
818
1117
 
819
- async getAttesters(): Promise<EthAddress[]> {
1118
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
820
1119
  const attesterSize = await this.getActiveAttesterCount();
821
1120
  const gse = new GSEContract(this.client, await this.getGSE());
822
- const ts = (await this.client.getBlock()).timestamp;
823
-
1121
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
824
1122
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
825
1123
  const chunks = chunk(indices, 1000);
826
1124
 
@@ -886,6 +1184,7 @@ export class RollupContract {
886
1184
  setupEpoch(l1TxUtils: L1TxUtils) {
887
1185
  return l1TxUtils.sendAndMonitorTransaction({
888
1186
  to: this.address,
1187
+ abi: RollupAbi,
889
1188
  data: encodeFunctionData({
890
1189
  abi: RollupAbi,
891
1190
  functionName: 'setupEpoch',
@@ -897,6 +1196,7 @@ export class RollupContract {
897
1196
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
898
1197
  return l1TxUtils.sendAndMonitorTransaction({
899
1198
  to: this.address,
1199
+ abi: RollupAbi,
900
1200
  data: encodeFunctionData({
901
1201
  abi: RollupAbi,
902
1202
  functionName: 'vote',
@@ -965,4 +1265,72 @@ export class RollupContract {
965
1265
  },
966
1266
  );
967
1267
  }
1268
+
1269
+ /** Fetches CheckpointProposed events within the given block range. */
1270
+ async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1271
+ const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
1272
+ return logs
1273
+ .filter(log => log.blockNumber! >= fromBlock && log.blockNumber! <= toBlock)
1274
+ .map(log => ({
1275
+ l1BlockNumber: log.blockNumber!,
1276
+ l1BlockHash: Buffer32.fromString(log.blockHash!),
1277
+ l1TransactionHash: log.transactionHash!,
1278
+ args: {
1279
+ checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1280
+ archive: Fr.fromString(log.args.archive!),
1281
+ versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
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
+ })(),
1298
+ },
1299
+ }));
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
+ }
968
1336
  }