@aztec/ethereum 0.0.1-commit.3fd054f6 → 0.0.1-commit.431c48d

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 (144) hide show
  1. package/dest/client.d.ts +19 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +52 -3
  4. package/dest/config.d.ts +15 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +29 -9
  7. package/dest/contracts/chain_state_override.d.ts +78 -0
  8. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  9. package/dest/contracts/chain_state_override.js +137 -0
  10. package/dest/contracts/empire_base.d.ts +1 -3
  11. package/dest/contracts/empire_base.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_price_oracle.d.ts +6 -2
  13. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -1
  14. package/dest/contracts/fee_asset_price_oracle.js +11 -6
  15. package/dest/contracts/governance.d.ts +135 -35
  16. package/dest/contracts/governance.d.ts.map +1 -1
  17. package/dest/contracts/governance.js +226 -15
  18. package/dest/contracts/governance_proposer.d.ts +33 -4
  19. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  20. package/dest/contracts/governance_proposer.js +52 -13
  21. package/dest/contracts/gse.d.ts +4 -2
  22. package/dest/contracts/gse.d.ts.map +1 -1
  23. package/dest/contracts/gse.js +2 -2
  24. package/dest/contracts/inbox.d.ts +3 -3
  25. package/dest/contracts/inbox.d.ts.map +1 -1
  26. package/dest/contracts/inbox.js +12 -7
  27. package/dest/contracts/index.d.ts +3 -3
  28. package/dest/contracts/index.d.ts.map +1 -1
  29. package/dest/contracts/index.js +2 -2
  30. package/dest/contracts/multicall.d.ts +79 -13
  31. package/dest/contracts/multicall.d.ts.map +1 -1
  32. package/dest/contracts/multicall.js +117 -95
  33. package/dest/contracts/outbox.d.ts +27 -4
  34. package/dest/contracts/outbox.d.ts.map +1 -1
  35. package/dest/contracts/outbox.js +34 -7
  36. package/dest/contracts/rollup.d.ts +5418 -28
  37. package/dest/contracts/rollup.d.ts.map +1 -1
  38. package/dest/contracts/rollup.js +389 -95
  39. package/dest/contracts/slashing_proposer.d.ts +171 -0
  40. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  41. package/dest/contracts/slashing_proposer.js +753 -0
  42. package/dest/contracts/watch_event.d.ts +36 -0
  43. package/dest/contracts/watch_event.d.ts.map +1 -0
  44. package/dest/contracts/watch_event.js +66 -0
  45. package/dest/deploy_aztec_l1_contracts.d.ts +4 -7
  46. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  47. package/dest/deploy_aztec_l1_contracts.js +15 -8
  48. package/dest/foundry_binary.d.ts +13 -0
  49. package/dest/foundry_binary.d.ts.map +1 -0
  50. package/dest/foundry_binary.js +52 -0
  51. package/dest/generated/l1-contracts-defaults.d.ts +2 -2
  52. package/dest/generated/l1-contracts-defaults.js +2 -2
  53. package/dest/l1_artifacts.d.ts +14941 -16273
  54. package/dest/l1_artifacts.d.ts.map +1 -1
  55. package/dest/l1_artifacts.js +9 -24
  56. package/dest/l1_contract_addresses.d.ts +61 -65
  57. package/dest/l1_contract_addresses.d.ts.map +1 -1
  58. package/dest/l1_contract_addresses.js +101 -82
  59. package/dest/l1_reader.d.ts +3 -5
  60. package/dest/l1_reader.d.ts.map +1 -1
  61. package/dest/l1_reader.js +3 -6
  62. package/dest/l1_tx_utils/config.js +3 -3
  63. package/dest/l1_tx_utils/l1_tx_utils.d.ts +9 -1
  64. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  65. package/dest/l1_tx_utils/l1_tx_utils.js +115 -54
  66. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  67. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  68. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +49 -19
  69. package/dest/l1_tx_utils/tx_delayer.d.ts +5 -3
  70. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -1
  71. package/dest/l1_tx_utils/tx_delayer.js +7 -5
  72. package/dest/publisher_manager.d.ts +18 -6
  73. package/dest/publisher_manager.d.ts.map +1 -1
  74. package/dest/publisher_manager.js +44 -7
  75. package/dest/queries.d.ts +13 -2
  76. package/dest/queries.d.ts.map +1 -1
  77. package/dest/queries.js +62 -3
  78. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  79. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  80. package/dest/test/blob_kzg_warmup.js +64 -0
  81. package/dest/test/chain_monitor.d.ts +34 -2
  82. package/dest/test/chain_monitor.d.ts.map +1 -1
  83. package/dest/test/chain_monitor.js +76 -6
  84. package/dest/test/eth_cheat_codes.d.ts +26 -3
  85. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  86. package/dest/test/eth_cheat_codes.js +61 -40
  87. package/dest/test/index.d.ts +2 -1
  88. package/dest/test/index.d.ts.map +1 -1
  89. package/dest/test/index.js +1 -0
  90. package/dest/test/recording_rpc_server.d.ts +18 -0
  91. package/dest/test/recording_rpc_server.d.ts.map +1 -0
  92. package/dest/test/recording_rpc_server.js +68 -0
  93. package/dest/test/rollup_cheat_codes.d.ts +60 -3
  94. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  95. package/dest/test/rollup_cheat_codes.js +86 -7
  96. package/dest/test/start_anvil.d.ts +1 -1
  97. package/dest/test/start_anvil.d.ts.map +1 -1
  98. package/dest/test/start_anvil.js +41 -5
  99. package/dest/utils.d.ts +9 -2
  100. package/dest/utils.d.ts.map +1 -1
  101. package/dest/utils.js +45 -14
  102. package/package.json +6 -6
  103. package/src/client.ts +63 -1
  104. package/src/config.ts +44 -13
  105. package/src/contracts/chain_state_override.ts +200 -0
  106. package/src/contracts/empire_base.ts +0 -2
  107. package/src/contracts/fee_asset_price_oracle.ts +10 -5
  108. package/src/contracts/governance.ts +310 -10
  109. package/src/contracts/governance_proposer.ts +56 -13
  110. package/src/contracts/gse.ts +7 -2
  111. package/src/contracts/inbox.ts +13 -5
  112. package/src/contracts/index.ts +2 -2
  113. package/src/contracts/multicall.ts +179 -105
  114. package/src/contracts/outbox.ts +42 -8
  115. package/src/contracts/rollup.ts +429 -86
  116. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +119 -45
  117. package/src/contracts/watch_event.ts +110 -0
  118. package/src/deploy_aztec_l1_contracts.ts +19 -10
  119. package/src/foundry_binary.ts +57 -0
  120. package/src/generated/l1-contracts-defaults.ts +2 -2
  121. package/src/l1_artifacts.ts +12 -35
  122. package/src/l1_contract_addresses.ts +120 -94
  123. package/src/l1_reader.ts +4 -9
  124. package/src/l1_tx_utils/config.ts +3 -3
  125. package/src/l1_tx_utils/l1_tx_utils.ts +107 -50
  126. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +49 -13
  127. package/src/l1_tx_utils/tx_delayer.ts +16 -7
  128. package/src/publisher_manager.ts +54 -18
  129. package/src/queries.ts +70 -4
  130. package/src/test/blob_kzg_warmup.ts +65 -0
  131. package/src/test/chain_monitor.ts +93 -5
  132. package/src/test/eth_cheat_codes.ts +57 -43
  133. package/src/test/index.ts +1 -0
  134. package/src/test/recording_rpc_server.ts +69 -0
  135. package/src/test/rollup_cheat_codes.ts +115 -6
  136. package/src/test/start_anvil.ts +38 -6
  137. package/src/utils.ts +41 -17
  138. package/dest/contracts/empire_slashing_proposer.d.ts +0 -69
  139. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  140. package/dest/contracts/empire_slashing_proposer.js +0 -216
  141. package/dest/contracts/tally_slashing_proposer.d.ts +0 -140
  142. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  143. package/dest/contracts/tally_slashing_proposer.js +0 -320
  144. package/src/contracts/empire_slashing_proposer.ts +0 -265
@@ -376,25 +376,21 @@ import { Buffer32 } from '@aztec/foundation/buffer';
376
376
  import { Fr } from '@aztec/foundation/curves/bn254';
377
377
  import { memoize } from '@aztec/foundation/decorators';
378
378
  import { EthAddress } from '@aztec/foundation/eth-address';
379
+ import { createLogger } from '@aztec/foundation/log';
379
380
  import { makeBackoff, retry } from '@aztec/foundation/retry';
381
+ import { getErrorCause } from '@aztec/foundation/types';
380
382
  import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
381
383
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
382
384
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
383
385
  import chunk from 'lodash.chunk';
384
- import { encodeFunctionData, getContract, hexToBigInt, keccak256 } from 'viem';
386
+ import { ContractFunctionRevertedError, RpcRequestError, decodeErrorResult, encodeAbiParameters, encodeFunctionData, getContract, hexToBigInt, keccak256 } from 'viem';
385
387
  import { getPublicClient } from '../client.js';
386
388
  import { formatViemError } from '../utils.js';
387
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
388
389
  import { GSEContract } from './gse.js';
389
390
  import { SlasherContract } from './slasher_contract.js';
390
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
391
+ import { SlashingProposerContract } from './slashing_proposer.js';
391
392
  import { checkBlockTag } from './utils.js';
392
- export var SlashingProposerType = /*#__PURE__*/ function(SlashingProposerType) {
393
- SlashingProposerType[SlashingProposerType["None"] = 0] = "None";
394
- SlashingProposerType[SlashingProposerType["Tally"] = 1] = "Tally";
395
- SlashingProposerType[SlashingProposerType["Empire"] = 2] = "Empire";
396
- return SlashingProposerType;
397
- }({});
393
+ import { watchContractEvent } from './watch_event.js';
398
394
  /**
399
395
  * Status of a validator/attester in the staking system.
400
396
  * Matches the Status enum in StakingLib.sol
@@ -405,6 +401,38 @@ export var SlashingProposerType = /*#__PURE__*/ function(SlashingProposerType) {
405
401
  AttesterStatus[AttesterStatus["EXITING"] = 3] = "EXITING";
406
402
  return AttesterStatus;
407
403
  }({});
404
+ /** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */ export var TempCheckpointLogField = /*#__PURE__*/ function(TempCheckpointLogField) {
405
+ TempCheckpointLogField[TempCheckpointLogField["HeaderHash"] = 0] = "HeaderHash";
406
+ TempCheckpointLogField[TempCheckpointLogField["BlobCommitmentsHash"] = 1] = "BlobCommitmentsHash";
407
+ TempCheckpointLogField[TempCheckpointLogField["OutHash"] = 2] = "OutHash";
408
+ TempCheckpointLogField[TempCheckpointLogField["AttestationsHash"] = 3] = "AttestationsHash";
409
+ TempCheckpointLogField[TempCheckpointLogField["PayloadDigest"] = 4] = "PayloadDigest";
410
+ TempCheckpointLogField[TempCheckpointLogField["SlotNumber"] = 5] = "SlotNumber";
411
+ TempCheckpointLogField[TempCheckpointLogField["FeeHeader"] = 6] = "FeeHeader";
412
+ return TempCheckpointLogField;
413
+ }({});
414
+ const INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR = 'ValidatorSelection__InsufficientValidatorSetSize';
415
+ /** SlasherUpdated events are rare governance operations, so their watcher polls well below the client's interval. */ const SLASHER_UPDATED_POLLING_INTERVAL_MS = 60_000;
416
+ function isValidatorSelectionError(err, errorName) {
417
+ return getErrorCause(err, ContractFunctionRevertedError)?.data?.errorName === errorName || decodeRpcRequestErrorName(err) === errorName;
418
+ }
419
+ function decodeRpcRequestErrorName(err) {
420
+ const data = getErrorCause(err, RpcRequestError)?.data;
421
+ if (!isHexString(data)) {
422
+ return undefined;
423
+ }
424
+ try {
425
+ return decodeErrorResult({
426
+ abi: RollupAbi,
427
+ data
428
+ }).errorName;
429
+ } catch {
430
+ return undefined;
431
+ }
432
+ }
433
+ function isHexString(value) {
434
+ return typeof value === 'string' && value.startsWith('0x');
435
+ }
408
436
  export class RollupContract {
409
437
  client;
410
438
  static{
@@ -517,6 +545,7 @@ export class RollupContract {
517
545
  ], []));
518
546
  }
519
547
  rollup;
548
+ logger;
520
549
  static cachedStfStorageSlot;
521
550
  cachedEscapeHatch;
522
551
  static get checkBlobStorageSlot() {
@@ -535,12 +564,12 @@ export class RollupContract {
535
564
  }
536
565
  static getFromConfig(config) {
537
566
  const client = getPublicClient(config);
538
- const address = config.l1Contracts.rollupAddress.toString();
567
+ const address = config.rollupAddress.toString();
539
568
  return new RollupContract(client, address);
540
569
  }
541
570
  constructor(client, address){
542
571
  this.client = client;
543
- _initProto(this);
572
+ this.logger = (_initProto(this), createLogger('ethereum:rollup'));
544
573
  if (address instanceof EthAddress) {
545
574
  address = address.toString();
546
575
  }
@@ -565,34 +594,10 @@ export class RollupContract {
565
594
  return undefined;
566
595
  }
567
596
  const proposerAddress = await slasher.getProposer();
568
- const proposerAbi = [
569
- {
570
- type: 'function',
571
- name: 'SLASHING_PROPOSER_TYPE',
572
- inputs: [],
573
- outputs: [
574
- {
575
- name: '',
576
- type: 'uint8',
577
- internalType: 'enum SlasherFlavor'
578
- }
579
- ],
580
- stateMutability: 'view'
581
- }
582
- ];
583
- const proposer = getContract({
584
- address: proposerAddress.toString(),
585
- abi: proposerAbi,
586
- client: this.client
587
- });
588
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
589
- if (proposerType === 1..valueOf()) {
590
- return new TallySlashingProposerContract(this.client, proposerAddress);
591
- } else if (proposerType === 2..valueOf()) {
592
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
593
- } else {
594
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
597
+ if (proposerAddress.isZero()) {
598
+ return undefined;
595
599
  }
600
+ return new SlashingProposerContract(this.client, proposerAddress);
596
601
  }
597
602
  getL1StartBlock() {
598
603
  return this.rollup.read.L1_BLOCK_AT_GENESIS();
@@ -733,7 +738,11 @@ export class RollupContract {
733
738
  BigInt(epoch)
734
739
  ]);
735
740
  return isOpen;
736
- } catch {
741
+ } catch (err) {
742
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
743
+ epoch: Number(epoch),
744
+ error: err
745
+ });
737
746
  return false;
738
747
  }
739
748
  }
@@ -749,8 +758,9 @@ export class RollupContract {
749
758
  async getOwner() {
750
759
  return EthAddress.fromString(await this.rollup.read.owner());
751
760
  }
752
- async getActiveAttesterCount() {
753
- return Number(await this.rollup.read.getActiveAttesterCount());
761
+ async getActiveAttesterCount(options) {
762
+ await checkBlockTag(options?.blockNumber, this.client);
763
+ return Number(await this.rollup.read.getActiveAttesterCount(options));
754
764
  }
755
765
  async getSlashingProposerAddress() {
756
766
  const slasher = await this.getSlasherContract();
@@ -762,25 +772,40 @@ export class RollupContract {
762
772
  getCheckpointReward() {
763
773
  return this.rollup.read.getCheckpointReward();
764
774
  }
765
- async getCheckpointNumber() {
766
- return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
775
+ async getCheckpointNumber(options) {
776
+ await checkBlockTag(options?.blockNumber, this.client);
777
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
767
778
  }
768
779
  async getProvenCheckpointNumber(options) {
769
780
  await checkBlockTag(options?.blockNumber, this.client);
770
781
  return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
771
782
  }
772
- async getSlotNumber() {
773
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
783
+ async getSlotNumber(options) {
784
+ await checkBlockTag(options?.blockNumber, this.client);
785
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
774
786
  }
775
- async getL1FeesAt(timestamp) {
787
+ async getL1FeesAt(timestamp, options) {
788
+ await checkBlockTag(options?.blockNumber, this.client);
776
789
  const result = await this.rollup.read.getL1FeesAt([
777
790
  timestamp
778
- ]);
791
+ ], options);
779
792
  return {
780
793
  baseFee: result.baseFee,
781
794
  blobFee: result.blobFee
782
795
  };
783
796
  }
797
+ async getFeeHeader(checkpointNumber) {
798
+ const result = await this.rollup.read.getFeeHeader([
799
+ checkpointNumber
800
+ ]);
801
+ return {
802
+ excessMana: result.excessMana,
803
+ manaUsed: result.manaUsed,
804
+ ethPerFeeAsset: result.ethPerFeeAsset,
805
+ congestionCost: result.congestionCost,
806
+ proverCost: result.proverCost
807
+ };
808
+ }
784
809
  getEthPerFeeAsset() {
785
810
  return this.rollup.read.getEthPerFeeAsset();
786
811
  }
@@ -793,7 +818,7 @@ export class RollupContract {
793
818
  timestamp
794
819
  ]
795
820
  }).catch((e)=>{
796
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
821
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
797
822
  return {
798
823
  result: undefined
799
824
  };
@@ -820,7 +845,7 @@ export class RollupContract {
820
845
  functionName: 'getCurrentEpochCommittee',
821
846
  args: []
822
847
  }).catch((e)=>{
823
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
848
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
824
849
  return {
825
850
  result: undefined
826
851
  };
@@ -849,10 +874,11 @@ export class RollupContract {
849
874
  });
850
875
  return EthAddress.fromString(result);
851
876
  }
852
- async getCheckpoint(checkpointNumber) {
877
+ async getCheckpoint(checkpointNumber, options) {
878
+ await checkBlockTag(options?.blockNumber, this.client);
853
879
  const result = await this.rollup.read.getCheckpoint([
854
880
  BigInt(checkpointNumber)
855
- ]);
881
+ ], options);
856
882
  return {
857
883
  archive: Fr.fromString(result.archive),
858
884
  headerHash: Buffer32.fromString(result.headerHash),
@@ -882,6 +908,28 @@ export class RollupContract {
882
908
  0.5
883
909
  ]));
884
910
  }
911
+ /**
912
+ * Returns the effective pending checkpoint, accounting for potential prunes.
913
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
914
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
915
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
916
+ * @param options - Optional L1 block number to pin the queries to.
917
+ */ getEffectivePendingCheckpoint(atTimestamp, options) {
918
+ return retry(async ()=>{
919
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
920
+ const canPrune = await this.canPruneAtTime(timestamp, options);
921
+ if (canPrune) {
922
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
923
+ return await this.getCheckpoint(provenCheckpointNumber, options);
924
+ }
925
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
926
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
927
+ }, 'getting effective pending checkpoint', makeBackoff([
928
+ 0.5,
929
+ 0.5,
930
+ 0.5
931
+ ]));
932
+ }
885
933
  async getTips() {
886
934
  const { pending, proven } = await this.rollup.read.getTips();
887
935
  return {
@@ -966,7 +1014,7 @@ export class RollupContract {
966
1014
  * @return [slot, checkpointNumber, timeOfNextL1Slot] - If you can propose, the L2 slot number, checkpoint number and
967
1015
  * timestamp of the next L1 block
968
1016
  * @throws otherwise
969
- */ async canProposeAt(archive, account, timestamp, opts = {}) {
1017
+ */ async canProposeAt(archive, account, timestamp, stateOverride = []) {
970
1018
  const timeOfNextL1Slot = timestamp;
971
1019
  const who = typeof account === 'string' ? account : account.address;
972
1020
  try {
@@ -980,7 +1028,7 @@ export class RollupContract {
980
1028
  who
981
1029
  ],
982
1030
  account,
983
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber)
1031
+ stateOverride
984
1032
  });
985
1033
  return {
986
1034
  slot: SlotNumber.fromBigInt(slot),
@@ -992,11 +1040,15 @@ export class RollupContract {
992
1040
  }
993
1041
  }
994
1042
  /**
995
- * Returns a state override that sets the pending checkpoint number to the specified value. Useful for simulations.
996
- * Requires querying the current state of the contract to get the current proven checkpoint number, as they are both
997
- * stored in the same slot. If the argument is undefined, it returns an empty override.
998
- */ async makePendingCheckpointNumberOverride(forcePendingCheckpointNumber) {
999
- if (forcePendingCheckpointNumber === undefined) {
1043
+ * Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
1044
+ * Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
1045
+ * a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
1046
+ * value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
1047
+ *
1048
+ * Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
1049
+ * `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
1050
+ */ async makeChainTipsOverride(override) {
1051
+ if (override.pending === undefined && override.proven === undefined) {
1000
1052
  return [];
1001
1053
  }
1002
1054
  const slot = RollupContract.stfStorageSlot;
@@ -1004,8 +1056,15 @@ export class RollupContract {
1004
1056
  address: this.address,
1005
1057
  slot
1006
1058
  });
1007
- const currentProvenCheckpointNumber = currentValue ? hexToBigInt(currentValue) & (1n << 128n) - 1n : 0n;
1008
- const newValue = BigInt(forcePendingCheckpointNumber) << 128n | currentProvenCheckpointNumber;
1059
+ const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
1060
+ const currentPending = currentRaw >> 128n;
1061
+ const currentProven = currentRaw & (1n << 128n) - 1n;
1062
+ const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
1063
+ const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
1064
+ if (newProven > newPending) {
1065
+ throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
1066
+ }
1067
+ const newValue = newPending << 128n | newProven;
1009
1068
  return [
1010
1069
  {
1011
1070
  address: this.address,
@@ -1018,6 +1077,161 @@ export class RollupContract {
1018
1077
  }
1019
1078
  ];
1020
1079
  }
1080
+ /**
1081
+ * Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
1082
+ * the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
1083
+ * pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
1084
+ *
1085
+ * `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
1086
+ * never asserts against them, so leaving them at storage zero is harmless.
1087
+ */ async makeTempCheckpointLogOverride(checkpointNumber, fields) {
1088
+ const constants = await this.getRollupConstants();
1089
+ const slotAt = (field)=>`0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}`;
1090
+ const word = (v)=>`0x${v.toString(16).padStart(64, '0')}`;
1091
+ const stateDiff = [];
1092
+ if (fields.headerHash) {
1093
+ stateDiff.push({
1094
+ slot: slotAt(0),
1095
+ value: fields.headerHash.toString()
1096
+ });
1097
+ }
1098
+ if (fields.outHash) {
1099
+ stateDiff.push({
1100
+ slot: slotAt(2),
1101
+ value: fields.outHash.toString()
1102
+ });
1103
+ }
1104
+ if (fields.payloadDigest) {
1105
+ stateDiff.push({
1106
+ slot: slotAt(4),
1107
+ value: fields.payloadDigest.toString()
1108
+ });
1109
+ }
1110
+ if (fields.slotNumber !== undefined) {
1111
+ // CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
1112
+ // so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
1113
+ const slotNumber = BigInt(fields.slotNumber);
1114
+ if (slotNumber < 0n || slotNumber > 0xffffffffn) {
1115
+ throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
1116
+ }
1117
+ stateDiff.push({
1118
+ slot: slotAt(5),
1119
+ value: word(slotNumber)
1120
+ });
1121
+ }
1122
+ if (fields.feeHeader) {
1123
+ stateDiff.push({
1124
+ slot: slotAt(6),
1125
+ value: word(RollupContract.compressFeeHeader(fields.feeHeader))
1126
+ });
1127
+ }
1128
+ if (stateDiff.length === 0) {
1129
+ return [];
1130
+ }
1131
+ return [
1132
+ {
1133
+ address: this.address,
1134
+ stateDiff
1135
+ }
1136
+ ];
1137
+ }
1138
+ /**
1139
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
1140
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
1141
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
1142
+ */ makeArchiveOverride(checkpointNumber, archive) {
1143
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
1144
+ const archiveSlot = hexToBigInt(keccak256(encodeAbiParameters([
1145
+ {
1146
+ type: 'uint256'
1147
+ },
1148
+ {
1149
+ type: 'uint256'
1150
+ }
1151
+ ], [
1152
+ BigInt(checkpointNumber),
1153
+ archivesMappingBase
1154
+ ])));
1155
+ return [
1156
+ {
1157
+ address: this.address,
1158
+ stateDiff: [
1159
+ {
1160
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
1161
+ value: archive.toString()
1162
+ }
1163
+ ]
1164
+ }
1165
+ ];
1166
+ }
1167
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */ static mergeStateOverrides(...overrides) {
1168
+ const byAddress = new Map();
1169
+ for (const override of overrides){
1170
+ for (const entry of override){
1171
+ const key = entry.address.toLowerCase();
1172
+ const existing = byAddress.get(key);
1173
+ if (existing) {
1174
+ existing.stateDiff.push(...entry.stateDiff ?? []);
1175
+ if (entry.balance !== undefined) {
1176
+ existing.balance = entry.balance;
1177
+ }
1178
+ } else {
1179
+ byAddress.set(key, {
1180
+ address: entry.address,
1181
+ balance: entry.balance,
1182
+ stateDiff: [
1183
+ ...entry.stateDiff ?? []
1184
+ ]
1185
+ });
1186
+ }
1187
+ }
1188
+ }
1189
+ return [
1190
+ ...byAddress.values()
1191
+ ];
1192
+ }
1193
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */ static compressFeeHeader(feeHeader) {
1194
+ const MASK_48_BITS = (1n << 48n) - 1n;
1195
+ const MASK_64_BITS = (1n << 64n) - 1n;
1196
+ const MASK_63_BITS = (1n << 63n) - 1n;
1197
+ let value = BigInt(feeHeader.manaUsed) & (1n << 32n) - 1n; // bits [0:31]
1198
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
1199
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
1200
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
1201
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
1202
+ value |= 1n << 255n; // preheat flag
1203
+ return value;
1204
+ }
1205
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
1206
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */ static computeChildFeeHeader(parentFeeHeader, childManaUsed, feeAssetPriceModifier, manaTarget) {
1207
+ const MIN_ETH_PER_FEE_ASSET = 100n;
1208
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
1209
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
1210
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
1211
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
1212
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
1213
+ const parentPrice = parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
1214
+ let newPrice;
1215
+ if (feeAssetPriceModifier >= 0n) {
1216
+ newPrice = parentPrice * (10_000n + feeAssetPriceModifier) / 10_000n;
1217
+ } else {
1218
+ const absMod = -feeAssetPriceModifier;
1219
+ newPrice = parentPrice * (10_000n - absMod) / 10_000n;
1220
+ }
1221
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
1222
+ newPrice = MIN_ETH_PER_FEE_ASSET;
1223
+ }
1224
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1225
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1226
+ }
1227
+ return {
1228
+ excessMana,
1229
+ manaUsed: childManaUsed,
1230
+ ethPerFeeAsset: newPrice,
1231
+ congestionCost: 0n,
1232
+ proverCost: 0n
1233
+ };
1234
+ }
1021
1235
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */ buildInvalidateBadAttestationRequest(checkpointNumber, attestationsAndSigners, committee, invalidIndex) {
1022
1236
  return {
1023
1237
  to: this.address,
@@ -1059,11 +1273,13 @@ export class RollupContract {
1059
1273
  prover
1060
1274
  ]);
1061
1275
  }
1062
- getManaMinFeeAt(timestamp, inFeeAsset) {
1276
+ getManaMinFeeAt(timestamp, inFeeAsset, stateOverride) {
1063
1277
  return this.rollup.read.getManaMinFeeAt([
1064
1278
  timestamp,
1065
1279
  inFeeAsset
1066
- ]);
1280
+ ], {
1281
+ stateOverride
1282
+ });
1067
1283
  }
1068
1284
  async getManaMinFeeComponentsAt(timestamp, inFeeAsset) {
1069
1285
  const result = await this.rollup.read.getManaMinFeeComponentsAt([
@@ -1127,14 +1343,23 @@ export class RollupContract {
1127
1343
  ]);
1128
1344
  }
1129
1345
  async getAttesters(timestamp) {
1130
- const attesterSize = await this.getActiveAttesterCount();
1346
+ // Pin every read to a single L1 block so the attester count and the chunked index reads
1347
+ // observe a consistent set. Without this, the count and each chunk default to `latest` and
1348
+ // can straddle a block boundary (or reorg), yielding an inconsistent or truncated set.
1349
+ const block = await this.client.getBlock();
1350
+ const blockNumber = block.number ?? undefined;
1351
+ const ts = timestamp ?? block.timestamp;
1352
+ const attesterSize = await this.getActiveAttesterCount({
1353
+ blockNumber
1354
+ });
1131
1355
  const gse = new GSEContract(this.client, await this.getGSE());
1132
- const ts = timestamp ?? (await this.client.getBlock()).timestamp;
1133
1356
  const indices = Array.from({
1134
1357
  length: attesterSize
1135
1358
  }, (_, i)=>BigInt(i));
1136
1359
  const chunks = chunk(indices, 1000);
1137
- const results = await Promise.all(chunks.map((chunk)=>gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)));
1360
+ const results = await Promise.all(chunks.map((chunk)=>gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, {
1361
+ blockNumber
1362
+ })));
1138
1363
  return results.flat().map((addr)=>EthAddress.fromString(addr));
1139
1364
  }
1140
1365
  async getAttesterView(address) {
@@ -1216,31 +1441,48 @@ export class RollupContract {
1216
1441
  })
1217
1442
  });
1218
1443
  }
1219
- listenToSlasherChanged(callback) {
1220
- return this.rollup.watchEvent.SlasherUpdated({}, {
1221
- onLogs: (logs)=>{
1222
- for (const log of logs){
1223
- const args = log.args;
1224
- if (args.oldSlasher && args.newSlasher) {
1225
- callback(args);
1226
- }
1444
+ /**
1445
+ * Watches for SlasherUpdated events. Events are delivered by polling `eth_getLogs`: a reorg may re-emit them and
1446
+ * removals are never reported, and events mined within roughly one polling interval of subscribing may be missed.
1447
+ * Slasher rotations are rare governance operations, so by default this polls much slower than the other watchers.
1448
+ */ listenToSlasherChanged(callback, options) {
1449
+ return watchContractEvent(this.client, this.logger, {
1450
+ address: this.address,
1451
+ abi: RollupAbi,
1452
+ eventName: 'SlasherUpdated',
1453
+ onLog: (log)=>{
1454
+ const { oldSlasher, newSlasher } = log.args;
1455
+ if (oldSlasher && newSlasher) {
1456
+ return callback({
1457
+ oldSlasher,
1458
+ newSlasher
1459
+ });
1227
1460
  }
1228
1461
  }
1462
+ }, {
1463
+ pollingIntervalMs: SLASHER_UPDATED_POLLING_INTERVAL_MS,
1464
+ ...options
1229
1465
  });
1230
1466
  }
1231
- listenToCheckpointInvalidated(callback) {
1232
- return this.rollup.watchEvent.CheckpointInvalidated({}, {
1233
- onLogs: (logs)=>{
1234
- for (const log of logs){
1235
- const args = log.args;
1236
- if (args.checkpointNumber !== undefined) {
1237
- callback({
1238
- checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber)
1239
- });
1240
- }
1467
+ /**
1468
+ * Watches for CheckpointInvalidated events. Events are delivered by polling `eth_getLogs`: a reorg may re-emit
1469
+ * them and removals are never reported, and events mined within roughly one polling interval of subscribing may
1470
+ * be missed.
1471
+ */ listenToCheckpointInvalidated(callback, options) {
1472
+ return watchContractEvent(this.client, this.logger, {
1473
+ address: this.address,
1474
+ abi: RollupAbi,
1475
+ eventName: 'CheckpointInvalidated',
1476
+ onLog: (log)=>{
1477
+ const { checkpointNumber } = log.args;
1478
+ if (checkpointNumber !== undefined) {
1479
+ return callback({
1480
+ checkpointNumber: CheckpointNumber.fromBigInt(checkpointNumber),
1481
+ event: log
1482
+ });
1241
1483
  }
1242
1484
  }
1243
- });
1485
+ }, options);
1244
1486
  }
1245
1487
  async getSlashEvents(l1BlockHash) {
1246
1488
  const events = await this.rollup.getEvents.Slashed({}, {
@@ -1252,18 +1494,31 @@ export class RollupContract {
1252
1494
  attester: EthAddress.fromString(event.args.attester)
1253
1495
  }));
1254
1496
  }
1255
- listenToSlash(callback) {
1256
- return this.rollup.watchEvent.Slashed({}, {
1497
+ /**
1498
+ * Watches for Slashed events. Events are delivered by polling `eth_getLogs`: a reorg may re-emit them and
1499
+ * removals are never reported, and events mined within roughly one polling interval of subscribing may be missed.
1500
+ */ listenToSlash(callback, options) {
1501
+ return watchContractEvent(this.client, this.logger, {
1502
+ address: this.address,
1503
+ abi: RollupAbi,
1504
+ eventName: 'Slashed',
1257
1505
  strict: true,
1258
- onLogs: (logs)=>{
1259
- for (const log of logs){
1260
- const args = log.args;
1261
- callback({
1262
- amount: args.amount,
1263
- attester: EthAddress.fromString(args.attester)
1264
- });
1265
- }
1266
- }
1506
+ onLog: (log)=>callback({
1507
+ amount: log.args.amount,
1508
+ attester: EthAddress.fromString(log.args.attester)
1509
+ })
1510
+ }, options);
1511
+ }
1512
+ /**
1513
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1514
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1515
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1516
+ * probe to detect RPC nodes that prune historical logs.
1517
+ */ async getOwnershipTransferredEventsAtDeploy() {
1518
+ const l1StartBlock = await this.getL1StartBlock();
1519
+ return await this.rollup.getEvents.OwnershipTransferred({}, {
1520
+ fromBlock: l1StartBlock,
1521
+ toBlock: l1StartBlock
1267
1522
  });
1268
1523
  }
1269
1524
  /** Fetches CheckpointProposed events within the given block range. */ async getCheckpointProposedEvents(fromBlock, toBlock) {
@@ -1294,4 +1549,43 @@ export class RollupContract {
1294
1549
  }
1295
1550
  }));
1296
1551
  }
1552
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */ static packChainTips(pendingCheckpointNumber, provenCheckpointNumber) {
1553
+ return pendingCheckpointNumber << 128n | provenCheckpointNumber & (1n << 128n) - 1n;
1554
+ }
1555
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */ static get chainTipsStorageSlot() {
1556
+ return BigInt(RollupContract.stfStorageSlot);
1557
+ }
1558
+ /**
1559
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1560
+ * @param checkpointNumber - The checkpoint number
1561
+ * @param field - The field within the CompressedTempCheckpointLog struct
1562
+ */ async getTempCheckpointLogStorageSlot(checkpointNumber, field) {
1563
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1564
+ this.getEpochDuration(),
1565
+ this.getProofSubmissionEpochs()
1566
+ ]);
1567
+ return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, {
1568
+ epochDuration,
1569
+ proofSubmissionEpochs
1570
+ });
1571
+ }
1572
+ computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants) {
1573
+ const fieldOffset = BigInt(field);
1574
+ const { epochDuration, proofSubmissionEpochs } = constants;
1575
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1576
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1577
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1578
+ const entryBase = BigInt(keccak256(encodeAbiParameters([
1579
+ {
1580
+ type: 'uint256'
1581
+ },
1582
+ {
1583
+ type: 'uint256'
1584
+ }
1585
+ ], [
1586
+ circularIndex,
1587
+ tempCheckpointLogsBase
1588
+ ])));
1589
+ return entryBase + fieldOffset;
1590
+ }
1297
1591
  }