@aztec/ethereum 0.0.1-commit.dbf9cec → 0.0.1-commit.ddcf04837

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 (95) 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 +8 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +11 -9
  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/inbox.d.ts +3 -3
  11. package/dest/contracts/inbox.d.ts.map +1 -1
  12. package/dest/contracts/inbox.js +5 -6
  13. package/dest/contracts/index.d.ts +3 -3
  14. package/dest/contracts/index.d.ts.map +1 -1
  15. package/dest/contracts/index.js +2 -2
  16. package/dest/contracts/multicall.d.ts +51 -2
  17. package/dest/contracts/multicall.d.ts.map +1 -1
  18. package/dest/contracts/multicall.js +85 -0
  19. package/dest/contracts/registry.d.ts +3 -1
  20. package/dest/contracts/registry.d.ts.map +1 -1
  21. package/dest/contracts/registry.js +30 -1
  22. package/dest/contracts/rollup.d.ts +83 -20
  23. package/dest/contracts/rollup.d.ts.map +1 -1
  24. package/dest/contracts/rollup.js +288 -58
  25. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
  26. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  27. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +13 -15
  28. package/dest/deploy_aztec_l1_contracts.d.ts +3 -6
  29. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  30. package/dest/deploy_aztec_l1_contracts.js +2 -4
  31. package/dest/generated/l1-contracts-defaults.d.ts +1 -1
  32. package/dest/generated/l1-contracts-defaults.js +1 -1
  33. package/dest/l1_artifacts.d.ts +8644 -15983
  34. package/dest/l1_artifacts.d.ts.map +1 -1
  35. package/dest/l1_artifacts.js +9 -24
  36. package/dest/l1_contract_addresses.d.ts +1 -5
  37. package/dest/l1_contract_addresses.d.ts.map +1 -1
  38. package/dest/l1_contract_addresses.js +0 -6
  39. package/dest/l1_reader.d.ts +3 -1
  40. package/dest/l1_reader.d.ts.map +1 -1
  41. package/dest/l1_reader.js +6 -1
  42. package/dest/l1_tx_utils/l1_tx_utils.d.ts +5 -1
  43. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  44. package/dest/l1_tx_utils/l1_tx_utils.js +40 -21
  45. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  46. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  47. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +8 -4
  48. package/dest/publisher_manager.d.ts +21 -7
  49. package/dest/publisher_manager.d.ts.map +1 -1
  50. package/dest/publisher_manager.js +81 -7
  51. package/dest/queries.js +3 -3
  52. package/dest/test/chain_monitor.d.ts +22 -3
  53. package/dest/test/chain_monitor.d.ts.map +1 -1
  54. package/dest/test/chain_monitor.js +33 -2
  55. package/dest/test/eth_cheat_codes.d.ts +6 -4
  56. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  57. package/dest/test/eth_cheat_codes.js +6 -4
  58. package/dest/test/rollup_cheat_codes.d.ts +5 -1
  59. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  60. package/dest/test/rollup_cheat_codes.js +9 -1
  61. package/dest/test/start_anvil.d.ts +23 -3
  62. package/dest/test/start_anvil.d.ts.map +1 -1
  63. package/dest/test/start_anvil.js +143 -29
  64. package/dest/utils.d.ts +1 -1
  65. package/dest/utils.d.ts.map +1 -1
  66. package/dest/utils.js +16 -12
  67. package/package.json +5 -7
  68. package/src/client.ts +10 -2
  69. package/src/config.ts +21 -13
  70. package/src/contracts/chain_state_override.ts +147 -0
  71. package/src/contracts/inbox.ts +4 -4
  72. package/src/contracts/index.ts +2 -2
  73. package/src/contracts/multicall.ts +65 -1
  74. package/src/contracts/registry.ts +31 -1
  75. package/src/contracts/rollup.ts +315 -61
  76. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +14 -16
  77. package/src/deploy_aztec_l1_contracts.ts +1 -5
  78. package/src/generated/l1-contracts-defaults.ts +1 -1
  79. package/src/l1_artifacts.ts +12 -35
  80. package/src/l1_contract_addresses.ts +0 -7
  81. package/src/l1_reader.ts +13 -1
  82. package/src/l1_tx_utils/l1_tx_utils.ts +31 -8
  83. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +8 -4
  84. package/src/publisher_manager.ts +105 -10
  85. package/src/queries.ts +3 -3
  86. package/src/test/chain_monitor.ts +60 -3
  87. package/src/test/eth_cheat_codes.ts +6 -4
  88. package/src/test/rollup_cheat_codes.ts +11 -1
  89. package/src/test/start_anvil.ts +177 -29
  90. package/src/utils.ts +17 -14
  91. package/dest/contracts/empire_slashing_proposer.d.ts +0 -69
  92. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  93. package/dest/contracts/empire_slashing_proposer.js +0 -216
  94. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  95. package/src/contracts/empire_slashing_proposer.ts +0 -265
@@ -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
@@ -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
  */
@@ -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,
@@ -392,16 +404,25 @@ export class RollupContract {
392
404
  epochDuration: number;
393
405
  proofSubmissionEpochs: number;
394
406
  targetCommitteeSize: number;
407
+ rollupManaLimit: number;
395
408
  }> {
396
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs, targetCommitteeSize] =
397
- await Promise.all([
398
- this.getL1StartBlock(),
399
- this.getL1GenesisTime(),
400
- this.getSlotDuration(),
401
- this.getEpochDuration(),
402
- this.getProofSubmissionEpochs(),
403
- this.getTargetCommitteeSize(),
404
- ]);
409
+ const [
410
+ l1StartBlock,
411
+ l1GenesisTime,
412
+ slotDuration,
413
+ epochDuration,
414
+ proofSubmissionEpochs,
415
+ targetCommitteeSize,
416
+ rollupManaLimit,
417
+ ] = await Promise.all([
418
+ this.getL1StartBlock(),
419
+ this.getL1GenesisTime(),
420
+ this.getSlotDuration(),
421
+ this.getEpochDuration(),
422
+ this.getProofSubmissionEpochs(),
423
+ this.getTargetCommitteeSize(),
424
+ this.getManaLimit(),
425
+ ]);
405
426
  return {
406
427
  l1StartBlock,
407
428
  l1GenesisTime,
@@ -409,6 +430,7 @@ export class RollupContract {
409
430
  epochDuration: Number(epochDuration),
410
431
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
411
432
  targetCommitteeSize,
433
+ rollupManaLimit: Number(rollupManaLimit),
412
434
  };
413
435
  }
414
436
 
@@ -463,7 +485,11 @@ export class RollupContract {
463
485
 
464
486
  const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
465
487
  return isOpen;
466
- } 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
+ });
467
493
  return false;
468
494
  }
469
495
  }
@@ -499,26 +525,41 @@ export class RollupContract {
499
525
  return this.rollup.read.getCheckpointReward();
500
526
  }
501
527
 
502
- async getCheckpointNumber(): Promise<CheckpointNumber> {
503
- 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));
504
531
  }
505
532
 
506
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
507
- 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));
508
536
  }
509
537
 
510
- async getSlotNumber(): Promise<SlotNumber> {
511
- 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));
512
541
  }
513
542
 
514
- async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
515
- 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);
516
546
  return {
517
547
  baseFee: result.baseFee,
518
548
  blobFee: result.blobFee,
519
549
  };
520
550
  }
521
551
 
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
+
522
563
  getEthPerFeeAsset(): Promise<bigint> {
523
564
  return this.rollup.read.getEthPerFeeAsset();
524
565
  }
@@ -593,8 +634,9 @@ export class RollupContract {
593
634
  return EthAddress.fromString(result);
594
635
  }
595
636
 
596
- async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
597
- 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);
598
640
  return {
599
641
  archive: Fr.fromString(result.archive),
600
642
  headerHash: Buffer32.fromString(result.headerHash),
@@ -627,6 +669,30 @@ export class RollupContract {
627
669
  );
628
670
  }
629
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
+
630
696
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
631
697
  const { pending, proven } = await this.rollup.read.getTips();
632
698
  return {
@@ -745,14 +811,13 @@ export class RollupContract {
745
811
  * timestamp of the next L1 block
746
812
  * @throws otherwise
747
813
  */
748
- public async canProposeAtNextEthBlock(
814
+ public async canProposeAt(
749
815
  archive: Buffer,
750
816
  account: `0x${string}` | Account,
751
- slotDuration: number,
752
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
817
+ timestamp: bigint,
818
+ stateOverride: StateOverride = [],
753
819
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
754
- const latestBlock = await this.client.getBlock();
755
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
820
+ const timeOfNextL1Slot = timestamp;
756
821
  const who = typeof account === 'string' ? account : account.address;
757
822
 
758
823
  try {
@@ -764,7 +829,7 @@ export class RollupContract {
764
829
  functionName: 'canProposeAtTime',
765
830
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
766
831
  account,
767
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
832
+ stateOverride,
768
833
  });
769
834
 
770
835
  return {
@@ -800,6 +865,151 @@ export class RollupContract {
800
865
  ];
801
866
  }
802
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
+
803
1013
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
804
1014
  public buildInvalidateBadAttestationRequest(
805
1015
  checkpointNumber: CheckpointNumber,
@@ -848,8 +1058,18 @@ export class RollupContract {
848
1058
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
849
1059
  }
850
1060
 
851
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
852
- 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
+ };
853
1073
  }
854
1074
 
855
1075
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -895,11 +1115,10 @@ export class RollupContract {
895
1115
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
896
1116
  }
897
1117
 
898
- async getAttesters(): Promise<EthAddress[]> {
1118
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
899
1119
  const attesterSize = await this.getActiveAttesterCount();
900
1120
  const gse = new GSEContract(this.client, await this.getGSE());
901
- const ts = (await this.client.getBlock()).timestamp;
902
-
1121
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
903
1122
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
904
1123
  const chunks = chunk(indices, 1000);
905
1124
 
@@ -1079,4 +1298,39 @@ export class RollupContract {
1079
1298
  },
1080
1299
  }));
1081
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
+ }
1082
1336
  }
@@ -7,7 +7,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
7
7
  import { Signature } from '@aztec/foundation/eth-signature';
8
8
  import { hexToBuffer } from '@aztec/foundation/string';
9
9
  import { SlasherAbi } from '@aztec/l1-artifacts/SlasherAbi';
10
- import { TallySlashingProposerAbi } from '@aztec/l1-artifacts/TallySlashingProposerAbi';
10
+ import { SlashingProposerAbi } from '@aztec/l1-artifacts/SlashingProposerAbi';
11
11
 
12
12
  import {
13
13
  type GetContractReturnType,
@@ -19,13 +19,11 @@ import {
19
19
  } from 'viem';
20
20
 
21
21
  /**
22
- * Wrapper around the TallySlashingProposer contract that provides
22
+ * Wrapper around the SlashingProposer contract that provides
23
23
  * a TypeScript interface for interacting with the consensus-based slashing system.
24
24
  */
25
- export class TallySlashingProposerContract {
26
- private readonly contract: GetContractReturnType<typeof TallySlashingProposerAbi, ViemClient>;
27
-
28
- public readonly type = 'tally' as const;
25
+ export class SlashingProposerContract {
26
+ private readonly contract: GetContractReturnType<typeof SlashingProposerAbi, ViemClient>;
29
27
 
30
28
  constructor(
31
29
  public readonly client: ViemClient,
@@ -33,7 +31,7 @@ export class TallySlashingProposerContract {
33
31
  ) {
34
32
  this.contract = getContract({
35
33
  address: typeof address === 'string' ? address : address.toString(),
36
- abi: TallySlashingProposerAbi,
34
+ abi: SlashingProposerAbi,
37
35
  client,
38
36
  });
39
37
  }
@@ -136,12 +134,12 @@ export class TallySlashingProposerContract {
136
134
 
137
135
  /** Tries to extract a VoteCast event from the given logs. */
138
136
  public tryExtractVoteCastEvent(logs: Log[]) {
139
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'VoteCast');
137
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'VoteCast');
140
138
  }
141
139
 
142
140
  /** Tries to extract a RoundExecuted event from the given logs. */
143
141
  public tryExtractRoundExecutedEvent(logs: Log[]) {
144
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'RoundExecuted');
142
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'RoundExecuted');
145
143
  }
146
144
 
147
145
  /**
@@ -161,9 +159,9 @@ export class TallySlashingProposerContract {
161
159
 
162
160
  return {
163
161
  to: this.contract.address,
164
- abi: TallySlashingProposerAbi,
162
+ abi: SlashingProposerAbi,
165
163
  data: encodeFunctionData({
166
- abi: TallySlashingProposerAbi,
164
+ abi: SlashingProposerAbi,
167
165
  functionName: 'vote',
168
166
  args: [votes, signature.toViemSignature()],
169
167
  }),
@@ -173,7 +171,7 @@ export class TallySlashingProposerContract {
173
171
  /** Returns the typed data definition to EIP712-sign for voting */
174
172
  public buildVoteTypedData(votes: Hex, slot: SlotNumber): TypedDataDefinition {
175
173
  const domain = {
176
- name: 'TallySlashingProposer',
174
+ name: 'SlashingProposer',
177
175
  version: '1',
178
176
  chainId: this.client.chain.id,
179
177
  verifyingContract: this.contract.address,
@@ -209,9 +207,9 @@ export class TallySlashingProposerContract {
209
207
  public buildVoteRequestWithSignature(votes: Hex, signature: { v: number; r: Hex; s: Hex }): L1TxRequest {
210
208
  return {
211
209
  to: this.contract.address,
212
- abi: TallySlashingProposerAbi,
210
+ abi: SlashingProposerAbi,
213
211
  data: encodeFunctionData({
214
- abi: TallySlashingProposerAbi,
212
+ abi: SlashingProposerAbi,
215
213
  functionName: 'vote',
216
214
  args: [votes, signature],
217
215
  }),
@@ -227,9 +225,9 @@ export class TallySlashingProposerContract {
227
225
  public buildExecuteRoundRequest(round: bigint, committees: EthAddress[][]): L1TxRequest {
228
226
  return {
229
227
  to: this.contract.address,
230
- abi: mergeAbis([TallySlashingProposerAbi, SlasherAbi]),
228
+ abi: mergeAbis([SlashingProposerAbi, SlasherAbi]),
231
229
  data: encodeFunctionData({
232
- abi: TallySlashingProposerAbi,
230
+ abi: SlashingProposerAbi,
233
231
  functionName: 'executeRound',
234
232
  args: [round, committees.map(c => c.map(addr => addr.toString()))],
235
233
  }),
@@ -230,7 +230,6 @@ export function computeValidatorData(operator: Operator): ValidatorJson {
230
230
  export interface RollupUpgradeAddresses {
231
231
  rollupAddress: string;
232
232
  verifierAddress: string;
233
- slashFactoryAddress: string;
234
233
  inboxAddress: string;
235
234
  outboxAddress: string;
236
235
  feeJuicePortalAddress: string;
@@ -243,7 +242,6 @@ export interface RollupUpgradeAddresses {
243
242
  export interface ForgeRollupUpgradeResult {
244
243
  rollupAddress: Hex;
245
244
  verifierAddress: Hex;
246
- slashFactoryAddress: Hex;
247
245
  inboxAddress: Hex;
248
246
  outboxAddress: Hex;
249
247
  feeJuicePortalAddress: Hex;
@@ -403,7 +401,6 @@ export async function deployAztecL1Contracts(
403
401
  governanceProposerAddress: EthAddress.fromString(result.governanceProposerAddress),
404
402
  governanceAddress: EthAddress.fromString(result.governanceAddress),
405
403
  stakingAssetAddress: EthAddress.fromString(result.stakingAssetAddress),
406
- slashFactoryAddress: result.slashFactoryAddress ? EthAddress.fromString(result.slashFactoryAddress) : undefined,
407
404
  feeAssetHandlerAddress: result.feeAssetHandlerAddress
408
405
  ? EthAddress.fromString(result.feeAssetHandlerAddress)
409
406
  : undefined,
@@ -585,7 +582,7 @@ export function getDeployRollupForUpgradeEnvVars(
585
582
  AZTEC_EXIT_DELAY_SECONDS: args.exitDelaySeconds.toString(),
586
583
  AZTEC_PROVING_COST_PER_MANA: args.provingCostPerMana.toString(),
587
584
  AZTEC_INITIAL_ETH_PER_FEE_ASSET: args.initialEthPerFeeAsset.toString(),
588
- AZTEC_SLASHER_FLAVOR: args.slasherFlavor,
585
+ AZTEC_SLASHER_ENABLED: args.slasherEnabled ? 'true' : 'false',
589
586
  AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS: args.slashingRoundSizeInEpochs.toString(),
590
587
  AZTEC_SLASHING_QUORUM: args.slashingQuorum?.toString(),
591
588
  AZTEC_SLASHING_OFFSET_IN_ROUNDS: args.slashingOffsetInRounds.toString(),
@@ -645,6 +642,5 @@ export const deployRollupForUpgrade = async (
645
642
 
646
643
  return {
647
644
  rollup,
648
- slashFactoryAddress: result.slashFactoryAddress,
649
645
  };
650
646
  };
@@ -18,7 +18,7 @@ export const l1ContractsDefaultEnv = {
18
18
  AZTEC_MANA_TARGET: 100000000,
19
19
  AZTEC_PROVING_COST_PER_MANA: 100,
20
20
  AZTEC_INITIAL_ETH_PER_FEE_ASSET: 10000000,
21
- AZTEC_SLASHER_FLAVOR: 'tally',
21
+ AZTEC_SLASHER_ENABLED: true,
22
22
  AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS: 4,
23
23
  AZTEC_SLASHING_LIFETIME_IN_ROUNDS: 5,
24
24
  AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS: 0,