@aztec/ethereum 0.0.1-commit.f146247c → 0.0.1-commit.f1b29a41e

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 (129) 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 +3 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +11 -3
  7. package/dest/contracts/empire_base.d.ts +3 -1
  8. package/dest/contracts/empire_base.d.ts.map +1 -1
  9. package/dest/contracts/empire_slashing_proposer.d.ts +3 -1
  10. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  11. package/dest/contracts/empire_slashing_proposer.js +9 -0
  12. package/dest/contracts/fee_asset_price_oracle.d.ts +101 -0
  13. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  14. package/dest/contracts/fee_asset_price_oracle.js +651 -0
  15. package/dest/contracts/governance.js +3 -3
  16. package/dest/contracts/governance_proposer.d.ts +3 -1
  17. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  18. package/dest/contracts/governance_proposer.js +9 -0
  19. package/dest/contracts/index.d.ts +2 -1
  20. package/dest/contracts/index.d.ts.map +1 -1
  21. package/dest/contracts/index.js +1 -0
  22. package/dest/contracts/multicall.d.ts +51 -2
  23. package/dest/contracts/multicall.d.ts.map +1 -1
  24. package/dest/contracts/multicall.js +85 -0
  25. package/dest/contracts/registry.d.ts +3 -1
  26. package/dest/contracts/registry.d.ts.map +1 -1
  27. package/dest/contracts/registry.js +30 -1
  28. package/dest/contracts/rollup.d.ts +46 -9
  29. package/dest/contracts/rollup.d.ts.map +1 -1
  30. package/dest/contracts/rollup.js +211 -17
  31. package/dest/deploy_aztec_l1_contracts.d.ts +2 -3
  32. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  33. package/dest/deploy_aztec_l1_contracts.js +37 -22
  34. package/dest/deploy_l1_contract.js +3 -3
  35. package/dest/generated/l1-contracts-defaults.d.ts +1 -1
  36. package/dest/generated/l1-contracts-defaults.js +1 -1
  37. package/dest/l1_artifacts.d.ts +1051 -42
  38. package/dest/l1_artifacts.d.ts.map +1 -1
  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/config.d.ts +7 -1
  43. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  44. package/dest/l1_tx_utils/config.js +14 -1
  45. package/dest/l1_tx_utils/factory.d.ts +18 -10
  46. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  47. package/dest/l1_tx_utils/factory.js +17 -7
  48. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  49. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  50. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  51. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  52. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  53. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  54. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  55. package/dest/l1_tx_utils/index-blobs.js +2 -2
  56. package/dest/l1_tx_utils/index.d.ts +2 -1
  57. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  58. package/dest/l1_tx_utils/index.js +1 -0
  59. package/dest/l1_tx_utils/l1_tx_utils.d.ts +20 -7
  60. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  61. package/dest/l1_tx_utils/l1_tx_utils.js +74 -50
  62. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  63. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  64. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +8 -4
  65. package/dest/l1_tx_utils/tx_delayer.d.ts +56 -0
  66. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  67. package/dest/{test → l1_tx_utils}/tx_delayer.js +62 -34
  68. package/dest/publisher_manager.d.ts +21 -7
  69. package/dest/publisher_manager.d.ts.map +1 -1
  70. package/dest/publisher_manager.js +81 -7
  71. package/dest/test/chain_monitor.d.ts +22 -3
  72. package/dest/test/chain_monitor.d.ts.map +1 -1
  73. package/dest/test/chain_monitor.js +33 -2
  74. package/dest/test/eth_cheat_codes.d.ts +6 -4
  75. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  76. package/dest/test/eth_cheat_codes.js +6 -4
  77. package/dest/test/index.d.ts +1 -3
  78. package/dest/test/index.d.ts.map +1 -1
  79. package/dest/test/index.js +0 -2
  80. package/dest/test/start_anvil.d.ts +23 -3
  81. package/dest/test/start_anvil.d.ts.map +1 -1
  82. package/dest/test/start_anvil.js +143 -29
  83. package/dest/test/upgrade_utils.js +2 -2
  84. package/dest/utils.d.ts +1 -1
  85. package/dest/utils.d.ts.map +1 -1
  86. package/dest/utils.js +16 -12
  87. package/package.json +5 -7
  88. package/src/client.ts +10 -2
  89. package/src/config.ts +15 -2
  90. package/src/contracts/empire_base.ts +2 -0
  91. package/src/contracts/empire_slashing_proposer.ts +6 -0
  92. package/src/contracts/fee_asset_price_oracle.ts +280 -0
  93. package/src/contracts/governance.ts +3 -3
  94. package/src/contracts/governance_proposer.ts +6 -0
  95. package/src/contracts/index.ts +1 -0
  96. package/src/contracts/multicall.ts +65 -1
  97. package/src/contracts/registry.ts +31 -1
  98. package/src/contracts/rollup.ts +240 -21
  99. package/src/deploy_aztec_l1_contracts.ts +60 -29
  100. package/src/deploy_l1_contract.ts +3 -3
  101. package/src/generated/l1-contracts-defaults.ts +1 -1
  102. package/src/l1_reader.ts +13 -1
  103. package/src/l1_tx_utils/config.ts +20 -0
  104. package/src/l1_tx_utils/factory.ts +31 -31
  105. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  106. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  107. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  108. package/src/l1_tx_utils/index-blobs.ts +2 -2
  109. package/src/l1_tx_utils/index.ts +1 -0
  110. package/src/l1_tx_utils/l1_tx_utils.ts +67 -29
  111. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +8 -4
  112. package/src/{test → l1_tx_utils}/tx_delayer.ts +78 -50
  113. package/src/publisher_manager.ts +105 -10
  114. package/src/test/chain_monitor.ts +60 -3
  115. package/src/test/eth_cheat_codes.ts +6 -4
  116. package/src/test/index.ts +0 -2
  117. package/src/test/start_anvil.ts +177 -29
  118. package/src/test/upgrade_utils.ts +2 -2
  119. package/src/utils.ts +17 -14
  120. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  121. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  122. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  123. package/dest/test/delayed_tx_utils.d.ts +0 -13
  124. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  125. package/dest/test/delayed_tx_utils.js +0 -28
  126. package/dest/test/tx_delayer.d.ts +0 -36
  127. package/dest/test/tx_delayer.d.ts.map +0 -1
  128. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  129. package/src/test/delayed_tx_utils.ts +0 -52
@@ -4,6 +4,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { memoize } from '@aztec/foundation/decorators';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
+ import { createLogger } from '@aztec/foundation/log';
7
8
  import { makeBackoff, retry } from '@aztec/foundation/retry';
8
9
  import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
9
10
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
@@ -16,6 +17,7 @@ import {
16
17
  type Hex,
17
18
  type StateOverride,
18
19
  type WatchContractEventReturnType,
20
+ encodeAbiParameters,
19
21
  encodeFunctionData,
20
22
  getContract,
21
23
  hexToBigInt,
@@ -134,6 +136,14 @@ export type L1FeeData = {
134
136
  blobFee: bigint;
135
137
  };
136
138
 
139
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
140
+ export type ManaMinFeeComponents = {
141
+ sequencerCost: bigint;
142
+ proverCost: bigint;
143
+ congestionCost: bigint;
144
+ congestionMultiplier: bigint;
145
+ };
146
+
137
147
  /**
138
148
  * Reward configuration for the rollup
139
149
  */
@@ -193,10 +203,10 @@ export type CheckpointProposedArgs = {
193
203
  checkpointNumber: CheckpointNumber;
194
204
  archive: Fr;
195
205
  versionedBlobHashes: Buffer[];
196
- /** Hash of attestations. Undefined for older events (backwards compatibility). */
197
- attestationsHash?: Buffer32;
198
- /** Digest of the payload. Undefined for older events (backwards compatibility). */
199
- payloadDigest?: Buffer32;
206
+ /** Hash of attestations emitted in the CheckpointProposed event. */
207
+ attestationsHash: Buffer32;
208
+ /** Digest of the payload emitted in the CheckpointProposed event. */
209
+ payloadDigest: Buffer32;
200
210
  };
201
211
 
202
212
  /** Log type for CheckpointProposed events. */
@@ -204,6 +214,7 @@ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
204
214
 
205
215
  export class RollupContract {
206
216
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
217
+ private readonly logger = createLogger('ethereum:rollup');
207
218
 
208
219
  private static cachedStfStorageSlot: Hex | undefined;
209
220
  private cachedEscapeHatch?: {
@@ -379,6 +390,20 @@ export class RollupContract {
379
390
  return Fr.fromString(await this.rollup.read.archiveAt([0n]));
380
391
  }
381
392
 
393
+ @memoize
394
+ async getVkTreeRoot(): Promise<Fr> {
395
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
396
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
397
+ return Fr.fromString(value ?? '0x0');
398
+ }
399
+
400
+ @memoize
401
+ async getProtocolContractsHash(): Promise<Fr> {
402
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
403
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
404
+ return Fr.fromString(value ?? '0x0');
405
+ }
406
+
382
407
  /**
383
408
  * Returns rollup constants used for epoch queries.
384
409
  * Return type is `L1RollupConstants` which is defined in stdlib,
@@ -391,13 +416,25 @@ export class RollupContract {
391
416
  slotDuration: number;
392
417
  epochDuration: number;
393
418
  proofSubmissionEpochs: number;
419
+ targetCommitteeSize: number;
420
+ rollupManaLimit: number;
394
421
  }> {
395
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
422
+ const [
423
+ l1StartBlock,
424
+ l1GenesisTime,
425
+ slotDuration,
426
+ epochDuration,
427
+ proofSubmissionEpochs,
428
+ targetCommitteeSize,
429
+ rollupManaLimit,
430
+ ] = await Promise.all([
396
431
  this.getL1StartBlock(),
397
432
  this.getL1GenesisTime(),
398
433
  this.getSlotDuration(),
399
434
  this.getEpochDuration(),
400
435
  this.getProofSubmissionEpochs(),
436
+ this.getTargetCommitteeSize(),
437
+ this.getManaLimit(),
401
438
  ]);
402
439
  return {
403
440
  l1StartBlock,
@@ -405,6 +442,8 @@ export class RollupContract {
405
442
  slotDuration,
406
443
  epochDuration: Number(epochDuration),
407
444
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
445
+ targetCommitteeSize,
446
+ rollupManaLimit: Number(rollupManaLimit),
408
447
  };
409
448
  }
410
449
 
@@ -459,7 +498,11 @@ export class RollupContract {
459
498
 
460
499
  const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
461
500
  return isOpen;
462
- } catch {
501
+ } catch (err) {
502
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
503
+ epoch: Number(epoch),
504
+ error: err,
505
+ });
463
506
  return false;
464
507
  }
465
508
  }
@@ -499,8 +542,9 @@ export class RollupContract {
499
542
  return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
500
543
  }
501
544
 
502
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
503
- return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber());
545
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
546
+ await checkBlockTag(options?.blockNumber, this.client);
547
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
504
548
  }
505
549
 
506
550
  async getSlotNumber(): Promise<SlotNumber> {
@@ -741,16 +785,23 @@ export class RollupContract {
741
785
  * timestamp of the next L1 block
742
786
  * @throws otherwise
743
787
  */
744
- public async canProposeAtNextEthBlock(
788
+ public async canProposeAt(
745
789
  archive: Buffer,
746
790
  account: `0x${string}` | Account,
747
- slotDuration: number,
748
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
791
+ timestamp: bigint,
792
+ opts: {
793
+ forcePendingCheckpointNumber?: CheckpointNumber;
794
+ forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
795
+ } = {},
749
796
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
750
- const latestBlock = await this.client.getBlock();
751
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
797
+ const timeOfNextL1Slot = timestamp;
752
798
  const who = typeof account === 'string' ? account : account.address;
753
799
 
800
+ const stateOverride = RollupContract.mergeStateOverrides(
801
+ await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
802
+ opts.forceArchive ? this.makeArchiveOverride(opts.forceArchive.checkpointNumber, opts.forceArchive.archive) : [],
803
+ );
804
+
754
805
  try {
755
806
  const {
756
807
  result: [slot, checkpointNumber],
@@ -760,7 +811,7 @@ export class RollupContract {
760
811
  functionName: 'canProposeAtTime',
761
812
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
762
813
  account,
763
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
814
+ stateOverride,
764
815
  });
765
816
 
766
817
  return {
@@ -796,6 +847,151 @@ export class RollupContract {
796
847
  ];
797
848
  }
798
849
 
850
+ /**
851
+ * Returns a state override that sets tempCheckpointLogs[checkpointNumber].feeHeader to the compressed fee header.
852
+ * Used when simulating a propose call where the parent checkpoint hasn't landed on L1 yet (pipelining).
853
+ */
854
+ public async makeFeeHeaderOverride(checkpointNumber: CheckpointNumber, feeHeader: FeeHeader): Promise<StateOverride> {
855
+ const { epochDuration, proofSubmissionEpochs } = await this.getRollupConstants();
856
+ const roundaboutSize = BigInt(epochDuration * (proofSubmissionEpochs + 1) + 1);
857
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
858
+
859
+ // tempCheckpointLogs is at offset 2 in RollupStore
860
+ const tempCheckpointLogsMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 2n;
861
+
862
+ // Solidity mapping slot: keccak256(abi.encode(key, baseSlot))
863
+ const structBaseSlot = hexToBigInt(
864
+ keccak256(
865
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsMappingBase]),
866
+ ),
867
+ );
868
+
869
+ // feeHeader is the 7th field (offset 6) in CompressedTempCheckpointLog
870
+ const feeHeaderSlot = structBaseSlot + 6n;
871
+ const compressed = RollupContract.compressFeeHeader(feeHeader);
872
+
873
+ return [
874
+ {
875
+ address: this.address,
876
+ stateDiff: [
877
+ {
878
+ slot: `0x${feeHeaderSlot.toString(16).padStart(64, '0')}`,
879
+ value: `0x${compressed.toString(16).padStart(64, '0')}`,
880
+ },
881
+ ],
882
+ },
883
+ ];
884
+ }
885
+
886
+ /**
887
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
888
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
889
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
890
+ */
891
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
892
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
893
+ const archiveSlot = hexToBigInt(
894
+ keccak256(
895
+ encodeAbiParameters(
896
+ [{ type: 'uint256' }, { type: 'uint256' }],
897
+ [BigInt(checkpointNumber), archivesMappingBase],
898
+ ),
899
+ ),
900
+ );
901
+ return [
902
+ {
903
+ address: this.address,
904
+ stateDiff: [
905
+ {
906
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
907
+ value: archive.toString(),
908
+ },
909
+ ],
910
+ },
911
+ ];
912
+ }
913
+
914
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
915
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
916
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
917
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
918
+ for (const override of overrides) {
919
+ for (const entry of override) {
920
+ const key = entry.address.toLowerCase();
921
+ const existing = byAddress.get(key);
922
+ if (existing) {
923
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
924
+ if (entry.balance !== undefined) {
925
+ existing.balance = entry.balance;
926
+ }
927
+ } else {
928
+ byAddress.set(key, {
929
+ address: entry.address,
930
+ balance: entry.balance,
931
+ stateDiff: [...(entry.stateDiff ?? [])],
932
+ });
933
+ }
934
+ }
935
+ }
936
+ return [...byAddress.values()];
937
+ }
938
+
939
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
940
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
941
+ const MASK_48_BITS = (1n << 48n) - 1n;
942
+ const MASK_64_BITS = (1n << 64n) - 1n;
943
+ const MASK_63_BITS = (1n << 63n) - 1n;
944
+
945
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
946
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
947
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
948
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
949
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
950
+ value |= 1n << 255n; // preheat flag
951
+ return value;
952
+ }
953
+
954
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
955
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
956
+ public static computeChildFeeHeader(
957
+ parentFeeHeader: FeeHeader,
958
+ childManaUsed: bigint,
959
+ feeAssetPriceModifier: bigint,
960
+ manaTarget: bigint,
961
+ ): FeeHeader {
962
+ const MIN_ETH_PER_FEE_ASSET = 100n;
963
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
964
+
965
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
966
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
967
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
968
+
969
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
970
+ const parentPrice =
971
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
972
+ let newPrice: bigint;
973
+ if (feeAssetPriceModifier >= 0n) {
974
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
975
+ } else {
976
+ const absMod = -feeAssetPriceModifier;
977
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
978
+ }
979
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
980
+ newPrice = MIN_ETH_PER_FEE_ASSET;
981
+ }
982
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
983
+ newPrice = MAX_ETH_PER_FEE_ASSET;
984
+ }
985
+
986
+ return {
987
+ excessMana,
988
+ manaUsed: childManaUsed,
989
+ ethPerFeeAsset: newPrice,
990
+ congestionCost: 0n,
991
+ proverCost: 0n,
992
+ };
993
+ }
994
+
799
995
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
800
996
  public buildInvalidateBadAttestationRequest(
801
997
  checkpointNumber: CheckpointNumber,
@@ -844,8 +1040,18 @@ export class RollupContract {
844
1040
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
845
1041
  }
846
1042
 
847
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
848
- return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
1043
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1044
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
1045
+ }
1046
+
1047
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1048
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1049
+ return {
1050
+ sequencerCost: result.sequencerCost,
1051
+ proverCost: result.proverCost,
1052
+ congestionCost: result.congestionCost,
1053
+ congestionMultiplier: result.congestionMultiplier,
1054
+ };
849
1055
  }
850
1056
 
851
1057
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
@@ -891,11 +1097,10 @@ export class RollupContract {
891
1097
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
892
1098
  }
893
1099
 
894
- async getAttesters(): Promise<EthAddress[]> {
1100
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
895
1101
  const attesterSize = await this.getActiveAttesterCount();
896
1102
  const gse = new GSEContract(this.client, await this.getGSE());
897
- const ts = (await this.client.getBlock()).timestamp;
898
-
1103
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
899
1104
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
900
1105
  const chunks = chunk(indices, 1000);
901
1106
 
@@ -1056,8 +1261,22 @@ export class RollupContract {
1056
1261
  checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1057
1262
  archive: Fr.fromString(log.args.archive!),
1058
1263
  versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1059
- attestationsHash: log.args.attestationsHash ? Buffer32.fromString(log.args.attestationsHash) : undefined,
1060
- payloadDigest: log.args.payloadDigest ? Buffer32.fromString(log.args.payloadDigest) : undefined,
1264
+ attestationsHash: (() => {
1265
+ if (!log.args.attestationsHash) {
1266
+ throw new Error(
1267
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1268
+ );
1269
+ }
1270
+ return Buffer32.fromString(log.args.attestationsHash);
1271
+ })(),
1272
+ payloadDigest: (() => {
1273
+ if (!log.args.payloadDigest) {
1274
+ throw new Error(
1275
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1276
+ );
1277
+ }
1278
+ return Buffer32.fromString(log.args.payloadDigest);
1279
+ })(),
1061
1280
  },
1062
1281
  }));
1063
1282
  }
@@ -15,7 +15,7 @@ import { tmpdir } from 'os';
15
15
  import { dirname, join, resolve } from 'path';
16
16
  import readline from 'readline';
17
17
  import type { Hex } from 'viem';
18
- import { foundry, mainnet, sepolia } from 'viem/chains';
18
+ import { mainnet, sepolia } from 'viem/chains';
19
19
 
20
20
  import { createEthereumChain, isAnvilTestChain } from './chain.js';
21
21
  import { createExtendedL1Client } from './client.js';
@@ -23,7 +23,6 @@ import type { L1ContractsConfig } from './config.js';
23
23
  import { deployMulticall3 } from './contracts/multicall.js';
24
24
  import { RollupContract } from './contracts/rollup.js';
25
25
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
26
- import type { L1TxUtilsConfig } from './l1_tx_utils/config.js';
27
26
  import type { ExtendedViemWalletClient } from './types.js';
28
27
 
29
28
  const logger = createLogger('ethereum:deploy_aztec_l1_contracts');
@@ -31,9 +30,9 @@ const logger = createLogger('ethereum:deploy_aztec_l1_contracts');
31
30
  const JSON_DEPLOY_RESULT_PREFIX = 'JSON DEPLOY RESULT:';
32
31
 
33
32
  /**
34
- * Runs a process with the given command, arguments, and environment.
35
- * If the process outputs a line starting with JSON_DEPLOY_RESULT_PREFIX,
36
- * the JSON is parsed and returned.
33
+ * Runs a process and parses JSON deploy results from stdout.
34
+ * Lines starting with JSON_DEPLOY_RESULT_PREFIX are parsed and returned.
35
+ * All other stdout goes to logger.info, stderr goes to logger.warn.
37
36
  */
38
37
  function runProcess<T>(
39
38
  command: string,
@@ -49,26 +48,41 @@ function runProcess<T>(
49
48
  });
50
49
 
51
50
  let result: T | undefined;
51
+ let parseError: Error | undefined;
52
+ let settled = false;
52
53
 
53
54
  readline.createInterface({ input: proc.stdout }).on('line', line => {
54
55
  const trimmedLine = line.trim();
55
56
  if (trimmedLine.startsWith(JSON_DEPLOY_RESULT_PREFIX)) {
56
57
  const jsonStr = trimmedLine.slice(JSON_DEPLOY_RESULT_PREFIX.length).trim();
57
- // TODO(AD): should this be a zod parse?
58
- result = JSON.parse(jsonStr);
58
+ try {
59
+ result = JSON.parse(jsonStr);
60
+ } catch {
61
+ parseError = new Error(`Failed to parse deploy result JSON: ${jsonStr.slice(0, 200)}`);
62
+ }
59
63
  } else {
60
64
  logger.info(line);
61
65
  }
62
66
  });
63
- readline.createInterface({ input: proc.stderr }).on('line', logger.error.bind(logger));
67
+ readline.createInterface({ input: proc.stderr }).on('line', logger.warn.bind(logger));
64
68
 
65
69
  proc.on('error', error => {
70
+ if (settled) {
71
+ return;
72
+ }
73
+ settled = true;
66
74
  reject(new Error(`Failed to spawn ${command}: ${error.message}`));
67
75
  });
68
76
 
69
77
  proc.on('close', code => {
78
+ if (settled) {
79
+ return;
80
+ }
81
+ settled = true;
70
82
  if (code !== 0) {
71
- reject(new Error(`${command} exited with code ${code}. See logs for details.\n`));
83
+ reject(new Error(`${command} exited with code ${code}`));
84
+ } else if (parseError) {
85
+ reject(parseError);
72
86
  } else {
73
87
  resolve(result);
74
88
  }
@@ -168,6 +182,9 @@ export function prepareL1ContractsForDeployment(): string {
168
182
  const foundryTomlPath = join(basePath, 'foundry.toml');
169
183
  let foundryToml = readFileSync(foundryTomlPath, 'utf-8');
170
184
  const solcPathMatch = foundryToml.match(/solc\s*=\s*"\.\/solc-([^"]+)"/);
185
+ // Did we find a hardcoded solc path that we need to make absolute?
186
+ // This code path happens in CI currently as we bundle solc there to avoid race conditions when
187
+ // downloading solc.
171
188
  if (solcPathMatch) {
172
189
  const solcVersion = solcPathMatch[1];
173
190
  const absoluteSolcPath = join(basePath, `solc-${solcVersion}`);
@@ -318,11 +335,8 @@ export async function deployAztecL1Contracts(
318
335
  );
319
336
  }
320
337
 
321
- // From heuristic testing. More caused issues with anvil.
322
- const MAGIC_ANVIL_BATCH_SIZE = 8;
323
- // Anvil seems to stall with unbounded batch size. Otherwise no max batch size is desirable.
338
+ const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js');
324
339
  const forgeArgs = [
325
- 'script',
326
340
  FORGE_SCRIPT,
327
341
  '--sig',
328
342
  'run()',
@@ -330,8 +344,6 @@ export async function deployAztecL1Contracts(
330
344
  privateKey,
331
345
  '--rpc-url',
332
346
  rpcUrl,
333
- '--broadcast',
334
- ...(chainId === foundry.id ? ['--batch-size', MAGIC_ANVIL_BATCH_SIZE.toString()] : []),
335
347
  ...(shouldVerify ? ['--verify'] : []),
336
348
  ];
337
349
  const forgeEnv = {
@@ -340,7 +352,12 @@ export async function deployAztecL1Contracts(
340
352
  FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
341
353
  ...getDeployAztecL1ContractsEnvVars(args),
342
354
  };
343
- const result = await runProcess<ForgeL1ContractsDeployResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
355
+ const result = await runProcess<ForgeL1ContractsDeployResult>(
356
+ process.execPath,
357
+ [scriptPath, ...forgeArgs],
358
+ forgeEnv,
359
+ l1ContractsPath,
360
+ );
344
361
  if (!result) {
345
362
  throw new Error('Forge script did not output deployment result');
346
363
  }
@@ -473,7 +490,25 @@ export type VerificationRecord = {
473
490
  libraries: VerificationLibraryEntry[];
474
491
  };
475
492
 
476
- export interface DeployAztecL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1TxUtilsConfig> {
493
+ export interface DeployAztecL1ContractsArgs
494
+ extends Omit<
495
+ L1ContractsConfig,
496
+ | 'gasLimitBufferPercentage'
497
+ | 'maxGwei'
498
+ | 'maxBlobGwei'
499
+ | 'priorityFeeBumpPercentage'
500
+ | 'priorityFeeRetryBumpPercentage'
501
+ | 'minimumPriorityFeePerGas'
502
+ | 'maxSpeedUpAttempts'
503
+ | 'checkIntervalMs'
504
+ | 'stallTimeMs'
505
+ | 'txTimeoutMs'
506
+ | 'cancelTxOnTimeout'
507
+ | 'txCancellationFinalTimeoutMs'
508
+ | 'txUnseenConsideredDroppedMs'
509
+ | 'enableDelayer'
510
+ | 'txDelayerMaxInclusionTimeIntoSlot'
511
+ > {
477
512
  /** The vk tree root. */
478
513
  vkTreeRoot: Fr;
479
514
  /** The hash of the protocol contracts. */
@@ -583,17 +618,8 @@ export const deployRollupForUpgrade = async (
583
618
  const FORGE_SCRIPT = 'script/deploy/DeployRollupForUpgrade.s.sol';
584
619
  await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
585
620
 
586
- const forgeArgs = [
587
- 'script',
588
- FORGE_SCRIPT,
589
- '--sig',
590
- 'run()',
591
- '--private-key',
592
- privateKey,
593
- '--rpc-url',
594
- rpcUrl,
595
- '--broadcast',
596
- ];
621
+ const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js');
622
+ const forgeArgs = [FORGE_SCRIPT, '--sig', 'run()', '--private-key', privateKey, '--rpc-url', rpcUrl];
597
623
  const forgeEnv = {
598
624
  FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
599
625
  // Env vars required by l1-contracts/script/deploy/RollupConfiguration.sol.
@@ -602,7 +628,12 @@ export const deployRollupForUpgrade = async (
602
628
  ...getDeployRollupForUpgradeEnvVars(args),
603
629
  };
604
630
 
605
- const result = await runProcess<ForgeRollupUpgradeResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
631
+ const result = await runProcess<ForgeRollupUpgradeResult>(
632
+ process.execPath,
633
+ [scriptPath, ...forgeArgs],
634
+ forgeEnv,
635
+ l1ContractsPath,
636
+ );
606
637
  if (!result) {
607
638
  throw new Error('Forge script did not output deployment result');
608
639
  }
@@ -24,7 +24,7 @@ import {
24
24
  } from './deploy_aztec_l1_contracts.js';
25
25
  import { RegisterNewRollupVersionPayloadArtifact } from './l1_artifacts.js';
26
26
  import { type L1TxUtilsConfig, getL1TxUtilsConfigEnvVars } from './l1_tx_utils/config.js';
27
- import { createL1TxUtilsFromViemWallet } from './l1_tx_utils/factory.js';
27
+ import { createL1TxUtils } from './l1_tx_utils/factory.js';
28
28
  import type { L1TxUtils } from './l1_tx_utils/l1_tx_utils.js';
29
29
  import type { GasPrice, L1TxConfig, L1TxRequest } from './l1_tx_utils/types.js';
30
30
  import type { ExtendedViemWalletClient } from './types.js';
@@ -46,7 +46,7 @@ export class L1Deployer {
46
46
  private createVerificationJson: boolean = false,
47
47
  ) {
48
48
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
49
- this.l1TxUtils = createL1TxUtilsFromViemWallet(
49
+ this.l1TxUtils = createL1TxUtils(
50
50
  this.client,
51
51
  { logger: this.logger, dateProvider },
52
52
  { ...this.txUtilsConfig, debugMaxGasLimit: acceleratedTestDeployments },
@@ -179,7 +179,7 @@ export async function deployL1Contract(
179
179
 
180
180
  if (!l1TxUtils) {
181
181
  const config = getL1TxUtilsConfigEnvVars();
182
- l1TxUtils = createL1TxUtilsFromViemWallet(
182
+ l1TxUtils = createL1TxUtils(
183
183
  extendedClient,
184
184
  { logger },
185
185
  { ...config, debugMaxGasLimit: acceleratedTestDeployments },
@@ -4,7 +4,7 @@
4
4
  /** Default L1 contracts configuration values from network-defaults.yml */
5
5
  export const l1ContractsDefaultEnv = {
6
6
  ETHEREUM_SLOT_DURATION: 12,
7
- AZTEC_SLOT_DURATION: 36,
7
+ AZTEC_SLOT_DURATION: 72,
8
8
  AZTEC_EPOCH_DURATION: 32,
9
9
  AZTEC_TARGET_COMMITTEE_SIZE: 48,
10
10
  AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET: 2,
package/src/l1_reader.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config';
1
+ import {
2
+ type ConfigMappingsType,
3
+ getConfigFromMappings,
4
+ numberConfigHelper,
5
+ optionalNumberConfigHelper,
6
+ } from '@aztec/foundation/config';
2
7
 
3
8
  import { type L1ContractAddresses, l1ContractAddressesMapping } from './l1_contract_addresses.js';
4
9
 
@@ -14,6 +19,8 @@ export interface L1ReaderConfig {
14
19
  l1Contracts: L1ContractAddresses;
15
20
  /** The polling interval viem uses in ms */
16
21
  viemPollingIntervalMS: number;
22
+ /** Timeout for HTTP requests to the L1 RPC node in ms. */
23
+ l1HttpTimeoutMS?: number;
17
24
  }
18
25
 
19
26
  export const l1ReaderConfigMappings: ConfigMappingsType<L1ReaderConfig> = {
@@ -43,6 +50,11 @@ export const l1ReaderConfigMappings: ConfigMappingsType<L1ReaderConfig> = {
43
50
  description: 'The polling interval viem uses in ms',
44
51
  ...numberConfigHelper(1_000),
45
52
  },
53
+ l1HttpTimeoutMS: {
54
+ env: 'ETHEREUM_HTTP_TIMEOUT_MS',
55
+ description: 'Timeout for HTTP requests to the L1 RPC node in ms.',
56
+ ...optionalNumberConfigHelper(),
57
+ },
46
58
  };
47
59
 
48
60
  export function getL1ReaderConfigFromEnv(): L1ReaderConfig {
@@ -5,6 +5,7 @@ import {
5
5
  getConfigFromMappings,
6
6
  getDefaultConfig,
7
7
  numberConfigHelper,
8
+ optionalNumberConfigHelper,
8
9
  } from '@aztec/foundation/config';
9
10
 
10
11
  export interface L1TxUtilsConfig {
@@ -60,6 +61,12 @@ export interface L1TxUtilsConfig {
60
61
  * How long a tx nonce can be unseen in the mempool before considering it dropped
61
62
  */
62
63
  txUnseenConsideredDroppedMs?: number;
64
+ /** Enable tx delayer. When true, wraps the viem client to intercept and delay txs. Test-only. */
65
+ enableDelayer?: boolean;
66
+ /** Max seconds into an L1 slot for tx inclusion. Txs sent later are deferred to next slot. Only used when enableDelayer is true. */
67
+ txDelayerMaxInclusionTimeIntoSlot?: number;
68
+ /** How many seconds an L1 slot lasts. */
69
+ ethereumSlotDuration?: number;
63
70
  }
64
71
 
65
72
  export const l1TxUtilsConfigMappings: ConfigMappingsType<L1TxUtilsConfig> = {
@@ -142,6 +149,19 @@ export const l1TxUtilsConfigMappings: ConfigMappingsType<L1TxUtilsConfig> = {
142
149
  env: 'L1_TX_MONITOR_TX_UNSEEN_CONSIDERED_DROPPED_MS',
143
150
  ...numberConfigHelper(6 * 12 * 1000), // 6 L1 blocks
144
151
  },
152
+ enableDelayer: {
153
+ description: 'Enable tx delayer for testing.',
154
+ ...booleanConfigHelper(false),
155
+ },
156
+ txDelayerMaxInclusionTimeIntoSlot: {
157
+ description: 'Max seconds into L1 slot for tx inclusion when delayer is enabled.',
158
+ ...optionalNumberConfigHelper(),
159
+ },
160
+ ethereumSlotDuration: {
161
+ env: 'ETHEREUM_SLOT_DURATION',
162
+ description: 'How many seconds an L1 slot lasts.',
163
+ ...numberConfigHelper(12),
164
+ },
145
165
  };
146
166
 
147
167
  // We abuse the fact that all mappings above have a non null default value and force-type this to Required