@aztec/ethereum 0.0.1-commit.c31f2472 → 0.0.1-commit.c52d6e7

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 (189) hide show
  1. package/dest/client.d.ts +25 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +60 -8
  4. package/dest/config.d.ts +29 -8
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +69 -14
  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/fee_asset_handler.d.ts +1 -1
  11. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_handler.js +2 -0
  13. package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
  14. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  15. package/dest/contracts/fee_asset_price_oracle.js +656 -0
  16. package/dest/contracts/governance.d.ts +135 -35
  17. package/dest/contracts/governance.d.ts.map +1 -1
  18. package/dest/contracts/governance.js +231 -18
  19. package/dest/contracts/governance_proposer.d.ts +33 -2
  20. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  21. package/dest/contracts/governance_proposer.js +58 -7
  22. package/dest/contracts/gse.d.ts +8 -2
  23. package/dest/contracts/gse.d.ts.map +1 -1
  24. package/dest/contracts/gse.js +11 -2
  25. package/dest/contracts/inbox.d.ts +3 -3
  26. package/dest/contracts/inbox.d.ts.map +1 -1
  27. package/dest/contracts/inbox.js +12 -7
  28. package/dest/contracts/index.d.ts +4 -3
  29. package/dest/contracts/index.d.ts.map +1 -1
  30. package/dest/contracts/index.js +3 -2
  31. package/dest/contracts/multicall.d.ts +130 -13
  32. package/dest/contracts/multicall.d.ts.map +1 -1
  33. package/dest/contracts/multicall.js +203 -93
  34. package/dest/contracts/outbox.d.ts +27 -4
  35. package/dest/contracts/outbox.d.ts.map +1 -1
  36. package/dest/contracts/outbox.js +34 -7
  37. package/dest/contracts/registry.d.ts +3 -1
  38. package/dest/contracts/registry.d.ts.map +1 -1
  39. package/dest/contracts/registry.js +30 -1
  40. package/dest/contracts/rollup.d.ts +5428 -31
  41. package/dest/contracts/rollup.d.ts.map +1 -1
  42. package/dest/contracts/rollup.js +414 -75
  43. package/dest/contracts/slashing_proposer.d.ts +170 -0
  44. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  45. package/dest/contracts/slashing_proposer.js +748 -0
  46. package/dest/deploy_aztec_l1_contracts.d.ts +19 -12
  47. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  48. package/dest/deploy_aztec_l1_contracts.js +65 -39
  49. package/dest/deploy_l1_contract.js +3 -3
  50. package/dest/foundry_binary.d.ts +13 -0
  51. package/dest/foundry_binary.d.ts.map +1 -0
  52. package/dest/foundry_binary.js +52 -0
  53. package/dest/l1_artifacts.d.ts +15526 -15844
  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 +5 -5
  60. package/dest/l1_reader.d.ts.map +1 -1
  61. package/dest/l1_reader.js +9 -7
  62. package/dest/l1_tx_utils/config.d.ts +7 -1
  63. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  64. package/dest/l1_tx_utils/config.js +17 -4
  65. package/dest/l1_tx_utils/constants.d.ts +1 -1
  66. package/dest/l1_tx_utils/constants.js +2 -2
  67. package/dest/l1_tx_utils/factory.d.ts +18 -10
  68. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  69. package/dest/l1_tx_utils/factory.js +17 -7
  70. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  71. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  72. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  73. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  75. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  76. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  77. package/dest/l1_tx_utils/index-blobs.js +2 -2
  78. package/dest/l1_tx_utils/index.d.ts +2 -1
  79. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  80. package/dest/l1_tx_utils/index.js +1 -0
  81. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +23 -4
  82. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -1
  83. package/dest/l1_tx_utils/l1_fee_analyzer.js +65 -3
  84. package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -7
  85. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  86. package/dest/l1_tx_utils/l1_tx_utils.js +185 -88
  87. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  88. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  89. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +13 -8
  90. package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
  91. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  92. package/dest/{test → l1_tx_utils}/tx_delayer.js +67 -37
  93. package/dest/l1_tx_utils/types.d.ts +27 -1
  94. package/dest/l1_tx_utils/types.d.ts.map +1 -1
  95. package/dest/l1_tx_utils/types.js +10 -0
  96. package/dest/publisher_manager.d.ts +34 -8
  97. package/dest/publisher_manager.d.ts.map +1 -1
  98. package/dest/publisher_manager.js +119 -8
  99. package/dest/queries.d.ts +13 -2
  100. package/dest/queries.d.ts.map +1 -1
  101. package/dest/queries.js +69 -4
  102. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  103. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  104. package/dest/test/blob_kzg_warmup.js +64 -0
  105. package/dest/test/chain_monitor.d.ts +55 -4
  106. package/dest/test/chain_monitor.d.ts.map +1 -1
  107. package/dest/test/chain_monitor.js +104 -3
  108. package/dest/test/eth_cheat_codes.d.ts +37 -6
  109. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  110. package/dest/test/eth_cheat_codes.js +89 -41
  111. package/dest/test/index.d.ts +2 -3
  112. package/dest/test/index.d.ts.map +1 -1
  113. package/dest/test/index.js +1 -2
  114. package/dest/test/rollup_cheat_codes.d.ts +60 -3
  115. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  116. package/dest/test/rollup_cheat_codes.js +86 -7
  117. package/dest/test/start_anvil.d.ts +23 -3
  118. package/dest/test/start_anvil.d.ts.map +1 -1
  119. package/dest/test/start_anvil.js +182 -32
  120. package/dest/test/upgrade_utils.js +2 -2
  121. package/dest/utils.d.ts +10 -2
  122. package/dest/utils.d.ts.map +1 -1
  123. package/dest/utils.js +94 -16
  124. package/package.json +6 -9
  125. package/src/client.ts +66 -2
  126. package/src/config.ts +97 -18
  127. package/src/contracts/chain_state_override.ts +200 -0
  128. package/src/contracts/fee_asset_handler.ts +2 -0
  129. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  130. package/src/contracts/governance.ts +315 -13
  131. package/src/contracts/governance_proposer.ts +62 -10
  132. package/src/contracts/gse.ts +19 -2
  133. package/src/contracts/inbox.ts +13 -5
  134. package/src/contracts/index.ts +3 -2
  135. package/src/contracts/multicall.ts +247 -103
  136. package/src/contracts/outbox.ts +42 -8
  137. package/src/contracts/registry.ts +31 -1
  138. package/src/contracts/rollup.ts +467 -78
  139. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +93 -28
  140. package/src/deploy_aztec_l1_contracts.ts +92 -47
  141. package/src/deploy_l1_contract.ts +3 -3
  142. package/src/foundry_binary.ts +57 -0
  143. package/src/l1_artifacts.ts +12 -35
  144. package/src/l1_contract_addresses.ts +120 -94
  145. package/src/l1_reader.ts +17 -10
  146. package/src/l1_tx_utils/config.ts +23 -3
  147. package/src/l1_tx_utils/constants.ts +2 -2
  148. package/src/l1_tx_utils/factory.ts +31 -31
  149. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  150. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  151. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  152. package/src/l1_tx_utils/index-blobs.ts +2 -2
  153. package/src/l1_tx_utils/index.ts +1 -0
  154. package/src/l1_tx_utils/l1_fee_analyzer.ts +75 -3
  155. package/src/l1_tx_utils/l1_tx_utils.ts +170 -68
  156. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +13 -8
  157. package/src/{test → l1_tx_utils}/tx_delayer.ts +93 -56
  158. package/src/l1_tx_utils/types.ts +32 -0
  159. package/src/publisher_manager.ts +143 -12
  160. package/src/queries.ts +76 -4
  161. package/src/test/blob_kzg_warmup.ts +65 -0
  162. package/src/test/chain_monitor.ts +148 -3
  163. package/src/test/eth_cheat_codes.ts +82 -45
  164. package/src/test/index.ts +1 -2
  165. package/src/test/rollup_cheat_codes.ts +115 -6
  166. package/src/test/start_anvil.ts +212 -32
  167. package/src/test/upgrade_utils.ts +2 -2
  168. package/src/utils.ts +98 -18
  169. package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
  170. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  171. package/dest/contracts/empire_slashing_proposer.js +0 -209
  172. package/dest/contracts/tally_slashing_proposer.d.ts +0 -140
  173. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  174. package/dest/contracts/tally_slashing_proposer.js +0 -313
  175. package/dest/generated/l1-contracts-defaults.d.ts +0 -30
  176. package/dest/generated/l1-contracts-defaults.d.ts.map +0 -1
  177. package/dest/generated/l1-contracts-defaults.js +0 -30
  178. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  179. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  180. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  181. package/dest/test/delayed_tx_utils.d.ts +0 -13
  182. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  183. package/dest/test/delayed_tx_utils.js +0 -28
  184. package/dest/test/tx_delayer.d.ts +0 -36
  185. package/dest/test/tx_delayer.d.ts.map +0 -1
  186. package/src/contracts/empire_slashing_proposer.ts +0 -270
  187. package/src/generated/l1-contracts-defaults.ts +0 -32
  188. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  189. package/src/test/delayed_tx_utils.ts +0 -52
@@ -1,12 +1,14 @@
1
1
  import type { L1TxRequest } from '@aztec/ethereum/l1-tx-utils';
2
2
  import type { ViemClient } from '@aztec/ethereum/types';
3
- import { tryExtractEvent } from '@aztec/ethereum/utils';
3
+ import { mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
4
4
  import { SlotNumber } from '@aztec/foundation/branded-types';
5
5
  import { Buffer32 } from '@aztec/foundation/buffer';
6
+ import { memoize } from '@aztec/foundation/decorators';
6
7
  import { EthAddress } from '@aztec/foundation/eth-address';
7
8
  import { Signature } from '@aztec/foundation/eth-signature';
8
9
  import { hexToBuffer } from '@aztec/foundation/string';
9
- import { TallySlashingProposerAbi } from '@aztec/l1-artifacts/TallySlashingProposerAbi';
10
+ import { SlasherAbi } from '@aztec/l1-artifacts/SlasherAbi';
11
+ import { SlashingProposerAbi } from '@aztec/l1-artifacts/SlashingProposerAbi';
10
12
 
11
13
  import {
12
14
  type GetContractReturnType,
@@ -18,13 +20,19 @@ import {
18
20
  } from 'viem';
19
21
 
20
22
  /**
21
- * Wrapper around the TallySlashingProposer contract that provides
23
+ * Wrapper around the SlashingProposer contract that provides
22
24
  * a TypeScript interface for interacting with the consensus-based slashing system.
23
25
  */
24
- export class TallySlashingProposerContract {
25
- private readonly contract: GetContractReturnType<typeof TallySlashingProposerAbi, ViemClient>;
26
+ export class SlashingProposerContract {
27
+ private readonly contract: GetContractReturnType<typeof SlashingProposerAbi, ViemClient>;
26
28
 
27
- public readonly type = 'tally' as const;
29
+ /**
30
+ * Slash target validators of the last round asked for. Safe to cache because a round's targets are the committees
31
+ * of epochs that had already ended when the round opened (see SLASH_OFFSET_IN_ROUNDS), and those committees are
32
+ * sampled from validator set and randao snapshots taken before the epoch started, so they cannot change while the
33
+ * round is live. The promise is cached rather than the result, so concurrent callers share one in-flight read.
34
+ */
35
+ private slashTargetValidators: { round: bigint; validators: Promise<EthAddress[]> } | undefined;
28
36
 
29
37
  constructor(
30
38
  public readonly client: ViemClient,
@@ -32,7 +40,7 @@ export class TallySlashingProposerContract {
32
40
  ) {
33
41
  this.contract = getContract({
34
42
  address: typeof address === 'string' ? address : address.toString(),
35
- abi: TallySlashingProposerAbi,
43
+ abi: SlashingProposerAbi,
36
44
  client,
37
45
  });
38
46
  }
@@ -65,6 +73,8 @@ export class TallySlashingProposerContract {
65
73
  return this.contract.read.EXECUTION_DELAY_IN_ROUNDS();
66
74
  }
67
75
 
76
+ /** Returns the slash amounts for the three slash unit levels. Immutable on the contract, so memoized. */
77
+ @memoize
68
78
  public getSlashingAmounts(): Promise<[bigint, bigint, bigint]> {
69
79
  return Promise.all([
70
80
  this.contract.read.SLASH_AMOUNT_SMALL(),
@@ -135,12 +145,12 @@ export class TallySlashingProposerContract {
135
145
 
136
146
  /** Tries to extract a VoteCast event from the given logs. */
137
147
  public tryExtractVoteCastEvent(logs: Log[]) {
138
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'VoteCast');
148
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'VoteCast');
139
149
  }
140
150
 
141
151
  /** Tries to extract a RoundExecuted event from the given logs. */
142
152
  public tryExtractRoundExecutedEvent(logs: Log[]) {
143
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'RoundExecuted');
153
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'RoundExecuted');
144
154
  }
145
155
 
146
156
  /**
@@ -160,8 +170,9 @@ export class TallySlashingProposerContract {
160
170
 
161
171
  return {
162
172
  to: this.contract.address,
173
+ abi: SlashingProposerAbi,
163
174
  data: encodeFunctionData({
164
- abi: TallySlashingProposerAbi,
175
+ abi: SlashingProposerAbi,
165
176
  functionName: 'vote',
166
177
  args: [votes, signature.toViemSignature()],
167
178
  }),
@@ -171,7 +182,7 @@ export class TallySlashingProposerContract {
171
182
  /** Returns the typed data definition to EIP712-sign for voting */
172
183
  public buildVoteTypedData(votes: Hex, slot: SlotNumber): TypedDataDefinition {
173
184
  const domain = {
174
- name: 'TallySlashingProposer',
185
+ name: 'SlashingProposer',
175
186
  version: '1',
176
187
  chainId: this.client.chain.id,
177
188
  verifyingContract: this.contract.address,
@@ -207,8 +218,9 @@ export class TallySlashingProposerContract {
207
218
  public buildVoteRequestWithSignature(votes: Hex, signature: { v: number; r: Hex; s: Hex }): L1TxRequest {
208
219
  return {
209
220
  to: this.contract.address,
221
+ abi: SlashingProposerAbi,
210
222
  data: encodeFunctionData({
211
- abi: TallySlashingProposerAbi,
223
+ abi: SlashingProposerAbi,
212
224
  functionName: 'vote',
213
225
  args: [votes, signature],
214
226
  }),
@@ -224,43 +236,79 @@ export class TallySlashingProposerContract {
224
236
  public buildExecuteRoundRequest(round: bigint, committees: EthAddress[][]): L1TxRequest {
225
237
  return {
226
238
  to: this.contract.address,
239
+ abi: mergeAbis([SlashingProposerAbi, SlasherAbi]),
227
240
  data: encodeFunctionData({
228
- abi: TallySlashingProposerAbi,
241
+ abi: SlashingProposerAbi,
229
242
  functionName: 'executeRound',
230
243
  args: [round, committees.map(c => c.map(addr => addr.toString()))],
231
244
  }),
232
245
  };
233
246
  }
234
247
 
248
+ /**
249
+ * Returns the validators eligible to be voted against in a round, in the order votes encode them. Cached for the
250
+ * last round asked for: reading them runs a committee sampling simulation on L1, and every vote of a round decodes
251
+ * against the same list, so a caller walking a round's votes would otherwise repeat that call per vote. Only the
252
+ * last round is kept since callers move forward round by round.
253
+ */
254
+ public getSlashTargetValidators(round: bigint): Promise<EthAddress[]> {
255
+ const cached = this.slashTargetValidators;
256
+ if (cached?.round === round) {
257
+ return cached.validators;
258
+ }
259
+
260
+ const entry = { round, validators: this.fetchSlashTargetValidators(round) };
261
+ this.slashTargetValidators = entry;
262
+ // A failed read must not stay cached, or every later vote of the round would replay the same rejection
263
+ entry.validators.catch(() => {
264
+ if (this.slashTargetValidators === entry) {
265
+ this.slashTargetValidators = undefined;
266
+ }
267
+ });
268
+ return entry.validators;
269
+ }
270
+
271
+ private async fetchSlashTargetValidators(round: bigint): Promise<EthAddress[]> {
272
+ const { result } = await this.contract.simulate.getSlashTargetCommittees([round]);
273
+ return result.flat().map(validator => EthAddress.fromString(validator));
274
+ }
275
+
276
+ /**
277
+ * Returns the slash amount voted for each target validator by a single vote of a round.
278
+ * @param index - Position of the vote within the round, from 0 (inclusive) to the round's vote count (exclusive)
279
+ */
280
+ public async getVoteAt(round: bigint, index: bigint): Promise<SlashVoteTarget[]> {
281
+ const [validators, vote, slashAmounts] = await Promise.all([
282
+ this.getSlashTargetValidators(round),
283
+ this.contract.read.getVotes([round, index]),
284
+ this.getSlashingAmounts(),
285
+ ]);
286
+ return decodeVote(vote, validators, slashAmounts);
287
+ }
288
+
235
289
  /** Returns the last vote emitted for a given round */
236
290
  public async getLastVote(round: bigint) {
237
291
  const { voteCount } = await this.getRound(round);
238
- const validators = (await this.contract.simulate.getSlashTargetCommittees([round])).result.flat();
239
- const vote = await this.contract.read.getVotes([round, voteCount - 1n]);
240
- const decoded = decodeSlashConsensusVotes(hexToBuffer(vote));
241
- const slashAmounts = await this.getSlashingAmounts();
242
- return decoded
243
- .map((units, i) => ({
244
- validator: EthAddress.fromString(validators[i]),
245
- slashAmount: slashAmounts[units - 1] ?? 0n,
246
- }))
247
- .filter(v => v.slashAmount > 0n);
292
+ return await this.getVoteAt(round, voteCount - 1n);
248
293
  }
249
294
 
250
295
  /**
251
296
  * Listen for VoteCast events
252
- * @param callback - Callback function to handle vote cast events
297
+ * @param callback - Callback receiving the round, the vote's index within the round as accepted by `getVoteAt`,
298
+ * and the proposer that cast it
253
299
  * @returns Unwatch function
254
300
  */
255
- public listenToVoteCast(callback: (args: { round: bigint; proposer: string }) => void): () => void {
301
+ public listenToVoteCast(
302
+ callback: (args: { round: bigint; voteIndex: bigint; proposer: string }) => void,
303
+ ): () => void {
256
304
  return this.contract.watchEvent.VoteCast(
257
305
  {},
258
306
  {
259
307
  onLogs: logs => {
260
308
  for (const log of logs) {
261
- const { round, proposer } = log.args;
262
- if (round !== undefined && proposer) {
263
- callback({ round, proposer });
309
+ const { round, voteIndex, proposer } = log.args;
310
+ if (round !== undefined && voteIndex !== undefined && proposer) {
311
+ callback({ round, voteIndex, proposer });
264
312
  }
265
313
  }
266
314
  },
@@ -292,6 +340,23 @@ export class TallySlashingProposerContract {
292
340
  }
293
341
  }
294
342
 
343
+ /**
344
+ * A validator targeted by a slashing vote, with the amount voted. The position is the validator's index in the
345
+ * round's flattened slash target committees — the unit the contract tallies quorum by. A validator sitting in
346
+ * several of the round's committees holds several positions, each with its own tally.
347
+ */
348
+ export type SlashVoteTarget = { validator: EthAddress; slashAmount: bigint; position: number };
349
+
350
+ function decodeVote(vote: Hex, validators: EthAddress[], slashAmounts: [bigint, bigint, bigint]): SlashVoteTarget[] {
351
+ return decodeSlashConsensusVotes(hexToBuffer(vote))
352
+ .map((units, position) => ({
353
+ validator: validators[position],
354
+ slashAmount: slashAmounts[units - 1] ?? 0n,
355
+ position,
356
+ }))
357
+ .filter(v => v.slashAmount > 0n);
358
+ }
359
+
295
360
  /**
296
361
  * Decodes a Buffer containing slash votes back into an array of numbers.
297
362
  * Each vote is represented as a 2-bit value (0, 1, 2, or 3) representing slashing units.
@@ -5,35 +5,37 @@ import { jsonStringify } from '@aztec/foundation/json-rpc';
5
5
  import { createLogger } from '@aztec/foundation/log';
6
6
  import { promiseWithResolvers } from '@aztec/foundation/promise';
7
7
  import type { Fr } from '@aztec/foundation/schemas';
8
- import { fileURLToPath } from '@aztec/foundation/url';
9
8
 
10
9
  import { bn254 } from '@noble/curves/bn254';
11
10
  import type { Abi, Narrow } from 'abitype';
12
11
  import { spawn } from 'child_process';
13
12
  import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs';
13
+ import { createRequire } from 'node:module';
14
14
  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';
22
- import type { L1ContractsConfig } from './config.js';
22
+ import { type L1ContractsConfig, assertValidSlotDurations } from './config.js';
23
23
  import { deployMulticall3 } from './contracts/multicall.js';
24
24
  import { RollupContract } from './contracts/rollup.js';
25
+ import { resolveFoundryBinary } from './foundry_binary.js';
25
26
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
26
- import type { L1TxUtilsConfig } from './l1_tx_utils/config.js';
27
27
  import type { ExtendedViemWalletClient } from './types.js';
28
28
 
29
29
  const logger = createLogger('ethereum:deploy_aztec_l1_contracts');
30
30
 
31
+ const require = createRequire(import.meta.url);
32
+
31
33
  const JSON_DEPLOY_RESULT_PREFIX = 'JSON DEPLOY RESULT:';
32
34
 
33
35
  /**
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.
36
+ * Runs a process and parses JSON deploy results from stdout.
37
+ * Lines starting with JSON_DEPLOY_RESULT_PREFIX are parsed and returned.
38
+ * All other stdout goes to logger.info, stderr goes to logger.warn.
37
39
  */
38
40
  function runProcess<T>(
39
41
  command: string,
@@ -49,26 +51,41 @@ function runProcess<T>(
49
51
  });
50
52
 
51
53
  let result: T | undefined;
54
+ let parseError: Error | undefined;
55
+ let settled = false;
52
56
 
53
57
  readline.createInterface({ input: proc.stdout }).on('line', line => {
54
58
  const trimmedLine = line.trim();
55
59
  if (trimmedLine.startsWith(JSON_DEPLOY_RESULT_PREFIX)) {
56
60
  const jsonStr = trimmedLine.slice(JSON_DEPLOY_RESULT_PREFIX.length).trim();
57
- // TODO(AD): should this be a zod parse?
58
- result = JSON.parse(jsonStr);
61
+ try {
62
+ result = JSON.parse(jsonStr);
63
+ } catch {
64
+ parseError = new Error(`Failed to parse deploy result JSON: ${jsonStr.slice(0, 200)}`);
65
+ }
59
66
  } else {
60
67
  logger.info(line);
61
68
  }
62
69
  });
63
- readline.createInterface({ input: proc.stderr }).on('line', logger.error.bind(logger));
70
+ readline.createInterface({ input: proc.stderr }).on('line', logger.warn.bind(logger));
64
71
 
65
72
  proc.on('error', error => {
73
+ if (settled) {
74
+ return;
75
+ }
76
+ settled = true;
66
77
  reject(new Error(`Failed to spawn ${command}: ${error.message}`));
67
78
  });
68
79
 
69
80
  proc.on('close', code => {
81
+ if (settled) {
82
+ return;
83
+ }
84
+ settled = true;
70
85
  if (code !== 0) {
71
- reject(new Error(`${command} exited with code ${code}. See logs for details.\n`));
86
+ reject(new Error(`${command} exited with code ${code}`));
87
+ } else if (parseError) {
88
+ reject(parseError);
72
89
  } else {
73
90
  resolve(result);
74
91
  }
@@ -79,11 +96,16 @@ function runProcess<T>(
79
96
 
80
97
  // Covers an edge where where we may have a cached BlobLib that is not meant for production.
81
98
  // Despite the profile apparently sometimes cached code remains (so says Lasse after his ignition-monorepo arc).
82
- async function maybeForgeForceProductionBuild(l1ContractsPath: string, script: string, chainId: number) {
99
+ async function maybeForgeForceProductionBuild(
100
+ forgeBin: string,
101
+ l1ContractsPath: string,
102
+ script: string,
103
+ chainId: number,
104
+ ) {
83
105
  if (chainId === mainnet.id) {
84
106
  logger.info(`Recompiling ${script} with production profile for mainnet deployment`);
85
107
  logger.info('This may take a minute but ensures production BlobLib is used.');
86
- await runProcess('forge', ['build', script, '--force'], { FOUNDRY_PROFILE: 'production' }, l1ContractsPath);
108
+ await runProcess(forgeBin, ['build', script, '--force'], { FOUNDRY_PROFILE: 'production' }, l1ContractsPath);
87
109
  }
88
110
  }
89
111
 
@@ -109,15 +131,13 @@ export interface ValidatorJson {
109
131
  }
110
132
 
111
133
  /**
112
- * Gets the path to the l1-contracts foundry artifacts directory.
113
- * These are copied from l1-contracts to yarn-project/l1-artifacts/l1-contracts
114
- * during build to make yarn-project self-contained.
134
+ * Gets the path to the l1-contracts foundry artifacts directory bundled inside @aztec/l1-artifacts.
135
+ * Resolved through the package (its "." export -> dest/index.js) so it works whether the package is
136
+ * linked via portal (monorepo) or installed under node_modules (published npm) — resolution follows
137
+ * the symlink in the portal case. The bundled foundry subtree sits alongside dest/, at <pkg>/l1-contracts.
115
138
  */
116
139
  export function getL1ContractsPath(): string {
117
- const currentDir = dirname(fileURLToPath(import.meta.url));
118
- // Go up from yarn-project/ethereum/dest to yarn-project, then to l1-artifacts/l1-contracts
119
- const l1ContractsPath = resolve(currentDir, '..', '..', 'l1-artifacts', 'l1-contracts');
120
- return l1ContractsPath;
140
+ return resolve(dirname(require.resolve('@aztec/l1-artifacts')), '..', 'l1-contracts');
121
141
  }
122
142
 
123
143
  // Cached deployment directory
@@ -168,6 +188,9 @@ export function prepareL1ContractsForDeployment(): string {
168
188
  const foundryTomlPath = join(basePath, 'foundry.toml');
169
189
  let foundryToml = readFileSync(foundryTomlPath, 'utf-8');
170
190
  const solcPathMatch = foundryToml.match(/solc\s*=\s*"\.\/solc-([^"]+)"/);
191
+ // Did we find a hardcoded solc path that we need to make absolute?
192
+ // This code path happens in CI currently as we bundle solc there to avoid race conditions when
193
+ // downloading solc.
171
194
  if (solcPathMatch) {
172
195
  const solcVersion = solcPathMatch[1];
173
196
  const absoluteSolcPath = join(basePath, `solc-${solcVersion}`);
@@ -213,7 +236,6 @@ export function computeValidatorData(operator: Operator): ValidatorJson {
213
236
  export interface RollupUpgradeAddresses {
214
237
  rollupAddress: string;
215
238
  verifierAddress: string;
216
- slashFactoryAddress: string;
217
239
  inboxAddress: string;
218
240
  outboxAddress: string;
219
241
  feeJuicePortalAddress: string;
@@ -226,7 +248,6 @@ export interface RollupUpgradeAddresses {
226
248
  export interface ForgeRollupUpgradeResult {
227
249
  rollupAddress: Hex;
228
250
  verifierAddress: Hex;
229
- slashFactoryAddress: Hex;
230
251
  inboxAddress: Hex;
231
252
  outboxAddress: Hex;
232
253
  feeJuicePortalAddress: Hex;
@@ -267,6 +288,7 @@ export async function deployAztecL1Contracts(
267
288
  args: DeployAztecL1ContractsArgs,
268
289
  ): Promise<DeployAztecL1ContractsReturnType> {
269
290
  logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
291
+ assertValidSlotDurations(args);
270
292
  if (args.initialValidators && args.initialValidators.length > 0 && args.existingTokenAddress) {
271
293
  throw new Error(
272
294
  'Cannot deploy with both initialValidators and existingTokenAddress. ' +
@@ -304,8 +326,9 @@ export async function deployAztecL1Contracts(
304
326
  // Use foundry-artifacts from l1-artifacts package
305
327
  const l1ContractsPath = prepareL1ContractsForDeployment();
306
328
 
329
+ const forgeBin = resolveFoundryBinary('forge');
307
330
  const FORGE_SCRIPT = 'script/deploy/DeployAztecL1Contracts.s.sol';
308
- await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
331
+ await maybeForgeForceProductionBuild(forgeBin, l1ContractsPath, FORGE_SCRIPT, chainId);
309
332
 
310
333
  // Verify contracts on Etherscan when on mainnet/sepolia and ETHERSCAN_API_KEY is available.
311
334
  const isVerifiableChain = chainId === mainnet.id || chainId === sepolia.id;
@@ -318,11 +341,8 @@ export async function deployAztecL1Contracts(
318
341
  );
319
342
  }
320
343
 
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.
344
+ const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js');
324
345
  const forgeArgs = [
325
- 'script',
326
346
  FORGE_SCRIPT,
327
347
  '--sig',
328
348
  'run()',
@@ -330,17 +350,22 @@ export async function deployAztecL1Contracts(
330
350
  privateKey,
331
351
  '--rpc-url',
332
352
  rpcUrl,
333
- '--broadcast',
334
- ...(chainId === foundry.id ? ['--batch-size', MAGIC_ANVIL_BATCH_SIZE.toString()] : []),
335
353
  ...(shouldVerify ? ['--verify'] : []),
336
354
  ];
337
355
  const forgeEnv = {
356
+ // Resolved forge binary picked up by forge_broadcast.js, so it works without forge on PATH.
357
+ FORGE_BIN: forgeBin,
338
358
  // Env vars required by l1-contracts/script/deploy/DeploymentConfiguration.sol.
339
359
  NETWORK: getActiveNetworkName(),
340
360
  FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
341
361
  ...getDeployAztecL1ContractsEnvVars(args),
342
362
  };
343
- const result = await runProcess<ForgeL1ContractsDeployResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
363
+ const result = await runProcess<ForgeL1ContractsDeployResult>(
364
+ process.execPath,
365
+ [scriptPath, ...forgeArgs],
366
+ forgeEnv,
367
+ l1ContractsPath,
368
+ );
344
369
  if (!result) {
345
370
  throw new Error('Forge script did not output deployment result');
346
371
  }
@@ -386,7 +411,6 @@ export async function deployAztecL1Contracts(
386
411
  governanceProposerAddress: EthAddress.fromString(result.governanceProposerAddress),
387
412
  governanceAddress: EthAddress.fromString(result.governanceAddress),
388
413
  stakingAssetAddress: EthAddress.fromString(result.stakingAssetAddress),
389
- slashFactoryAddress: result.slashFactoryAddress ? EthAddress.fromString(result.slashFactoryAddress) : undefined,
390
414
  feeAssetHandlerAddress: result.feeAssetHandlerAddress
391
415
  ? EthAddress.fromString(result.feeAssetHandlerAddress)
392
416
  : undefined,
@@ -473,7 +497,25 @@ export type VerificationRecord = {
473
497
  libraries: VerificationLibraryEntry[];
474
498
  };
475
499
 
476
- export interface DeployAztecL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1TxUtilsConfig> {
500
+ export interface DeployAztecL1ContractsArgs
501
+ extends Omit<
502
+ L1ContractsConfig,
503
+ | 'gasLimitBufferPercentage'
504
+ | 'maxGwei'
505
+ | 'maxBlobGwei'
506
+ | 'priorityFeeBumpPercentage'
507
+ | 'priorityFeeRetryBumpPercentage'
508
+ | 'minimumPriorityFeePerGas'
509
+ | 'maxSpeedUpAttempts'
510
+ | 'checkIntervalMs'
511
+ | 'stallTimeMs'
512
+ | 'txTimeoutMs'
513
+ | 'cancelTxOnTimeout'
514
+ | 'txCancellationFinalTimeoutMs'
515
+ | 'txUnseenConsideredDroppedMs'
516
+ | 'enableDelayer'
517
+ | 'txDelayerMaxInclusionTimeIntoSlot'
518
+ > {
477
519
  /** The vk tree root. */
478
520
  vkTreeRoot: Fr;
479
521
  /** The hash of the protocol contracts. */
@@ -550,13 +592,18 @@ export function getDeployRollupForUpgradeEnvVars(
550
592
  AZTEC_EXIT_DELAY_SECONDS: args.exitDelaySeconds.toString(),
551
593
  AZTEC_PROVING_COST_PER_MANA: args.provingCostPerMana.toString(),
552
594
  AZTEC_INITIAL_ETH_PER_FEE_ASSET: args.initialEthPerFeeAsset.toString(),
553
- AZTEC_SLASHER_FLAVOR: args.slasherFlavor,
595
+ AZTEC_SLASHER_ENABLED: args.slasherEnabled ? 'true' : 'false',
554
596
  AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS: args.slashingRoundSizeInEpochs.toString(),
555
597
  AZTEC_SLASHING_QUORUM: args.slashingQuorum?.toString(),
556
598
  AZTEC_SLASHING_OFFSET_IN_ROUNDS: args.slashingOffsetInRounds.toString(),
557
599
  AZTEC_SLASH_AMOUNT_SMALL: args.slashAmountSmall.toString(),
558
600
  AZTEC_SLASH_AMOUNT_MEDIUM: args.slashAmountMedium.toString(),
559
601
  AZTEC_SLASH_AMOUNT_LARGE: args.slashAmountLarge.toString(),
602
+ AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE: args.entryQueueBootstrapValidatorSetSize.toString(),
603
+ AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE: args.entryQueueBootstrapFlushSize.toString(),
604
+ AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN: args.entryQueueFlushSizeMin.toString(),
605
+ AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT: args.entryQueueFlushSizeQuotient.toString(),
606
+ AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE: args.entryQueueMaxFlushSize.toString(),
560
607
  } as const;
561
608
  }
562
609
 
@@ -580,21 +627,15 @@ export const deployRollupForUpgrade = async (
580
627
  // Use foundry-artifacts from l1-artifacts package
581
628
  const l1ContractsPath = prepareL1ContractsForDeployment();
582
629
 
630
+ const forgeBin = resolveFoundryBinary('forge');
583
631
  const FORGE_SCRIPT = 'script/deploy/DeployRollupForUpgrade.s.sol';
584
- await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
632
+ await maybeForgeForceProductionBuild(forgeBin, l1ContractsPath, FORGE_SCRIPT, chainId);
585
633
 
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
- ];
634
+ const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js');
635
+ const forgeArgs = [FORGE_SCRIPT, '--sig', 'run()', '--private-key', privateKey, '--rpc-url', rpcUrl];
597
636
  const forgeEnv = {
637
+ // Resolved forge binary picked up by forge_broadcast.js, so it works without forge on PATH.
638
+ FORGE_BIN: forgeBin,
598
639
  FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
599
640
  // Env vars required by l1-contracts/script/deploy/RollupConfiguration.sol.
600
641
  REGISTRY_ADDRESS: registryAddress.toString(),
@@ -602,7 +643,12 @@ export const deployRollupForUpgrade = async (
602
643
  ...getDeployRollupForUpgradeEnvVars(args),
603
644
  };
604
645
 
605
- const result = await runProcess<ForgeRollupUpgradeResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
646
+ const result = await runProcess<ForgeRollupUpgradeResult>(
647
+ process.execPath,
648
+ [scriptPath, ...forgeArgs],
649
+ forgeEnv,
650
+ l1ContractsPath,
651
+ );
606
652
  if (!result) {
607
653
  throw new Error('Forge script did not output deployment result');
608
654
  }
@@ -614,6 +660,5 @@ export const deployRollupForUpgrade = async (
614
660
 
615
661
  return {
616
662
  rollup,
617
- slashFactoryAddress: result.slashFactoryAddress,
618
663
  };
619
664
  };
@@ -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 },
@@ -0,0 +1,57 @@
1
+ import { spawnSync } from 'child_process';
2
+ import { accessSync, constants } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+
6
+ function isExecutable(path: string): boolean {
7
+ try {
8
+ accessSync(path, constants.X_OK);
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Locate a Foundry binary (`anvil`, `forge`, ...) without relying on the caller's PATH. Order:
17
+ * 1. `$<NAME>_BIN` (e.g. `$ANVIL_BIN`, `$FORGE_BIN`) — explicit override, e.g. for CI with a pinned
18
+ * version. Throws if set but not pointing at an executable, instead of silently falling back.
19
+ * 2. `~/.aztec/current/internal-bin/<name>` — where aztec-up installs it.
20
+ * 3. `~/.aztec/current/bin/aztec-<name>` — the publicly-exposed symlink.
21
+ * 4. `~/.foundry/bin/<name>` — standalone foundryup install.
22
+ * 5. `command -v <name>` — anything else on PATH.
23
+ *
24
+ * Throws with a directive message if none work.
25
+ */
26
+ export function resolveFoundryBinary(name: string): string {
27
+ const envVar = `${name.toUpperCase()}_BIN`;
28
+ const envBin = process.env[envVar];
29
+ if (envBin) {
30
+ if (!isExecutable(envBin)) {
31
+ throw new Error(`$${envVar} is set to ${envBin}, which does not exist or is not executable.`);
32
+ }
33
+ return envBin;
34
+ }
35
+
36
+ const candidates = [
37
+ join(homedir(), '.aztec', 'current', 'internal-bin', name),
38
+ join(homedir(), '.aztec', 'current', 'bin', `aztec-${name}`),
39
+ join(homedir(), '.foundry', 'bin', name),
40
+ ];
41
+ for (const path of candidates) {
42
+ if (isExecutable(path)) {
43
+ return path;
44
+ }
45
+ }
46
+
47
+ const which = spawnSync('sh', ['-c', `command -v ${name}`], { encoding: 'utf8' });
48
+ if (which.status === 0 && which.stdout.trim()) {
49
+ return which.stdout.trim();
50
+ }
51
+
52
+ throw new Error(
53
+ `${name} binary not found. Tried $${envVar}, ~/.aztec/current/internal-bin/${name}, ` +
54
+ `~/.aztec/current/bin/aztec-${name}, ~/.foundry/bin/${name}, and $PATH. ` +
55
+ `Install via \`aztec-up\` or set ${envVar} to a working binary.`,
56
+ );
57
+ }