@aztec/ethereum 2.0.3 → 2.1.0-rc.2

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 (124) hide show
  1. package/dest/config.d.ts +10 -5
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +27 -39
  4. package/dest/contracts/empire_base.d.ts +1 -1
  5. package/dest/contracts/empire_base.d.ts.map +1 -1
  6. package/dest/contracts/empire_slashing_proposer.d.ts +1 -1
  7. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  8. package/dest/contracts/fee_asset_handler.d.ts +3 -3
  9. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  10. package/dest/contracts/governance.js +7 -3
  11. package/dest/contracts/governance_proposer.d.ts +1 -2
  12. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  13. package/dest/contracts/governance_proposer.js +1 -2
  14. package/dest/contracts/multicall.d.ts +3 -5
  15. package/dest/contracts/multicall.d.ts.map +1 -1
  16. package/dest/contracts/multicall.js +6 -4
  17. package/dest/contracts/rollup.d.ts +13 -14
  18. package/dest/contracts/rollup.d.ts.map +1 -1
  19. package/dest/contracts/rollup.js +25 -67
  20. package/dest/contracts/slasher_contract.d.ts +10 -0
  21. package/dest/contracts/slasher_contract.d.ts.map +1 -1
  22. package/dest/contracts/slasher_contract.js +18 -0
  23. package/dest/deploy_l1_contracts.d.ts +18 -4
  24. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  25. package/dest/deploy_l1_contracts.js +316 -159
  26. package/dest/index.d.ts +1 -1
  27. package/dest/index.d.ts.map +1 -1
  28. package/dest/index.js +1 -1
  29. package/dest/l1_artifacts.d.ts +8019 -5948
  30. package/dest/l1_artifacts.d.ts.map +1 -1
  31. package/dest/l1_artifacts.js +6 -1
  32. package/dest/l1_contract_addresses.d.ts +5 -1
  33. package/dest/l1_contract_addresses.d.ts.map +1 -1
  34. package/dest/l1_contract_addresses.js +2 -1
  35. package/dest/l1_tx_utils/config.d.ts +59 -0
  36. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  37. package/dest/l1_tx_utils/config.js +73 -0
  38. package/dest/l1_tx_utils/constants.d.ts +6 -0
  39. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  40. package/dest/l1_tx_utils/constants.js +14 -0
  41. package/dest/l1_tx_utils/factory.d.ts +24 -0
  42. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  43. package/dest/l1_tx_utils/factory.js +12 -0
  44. package/dest/l1_tx_utils/index.d.ts +10 -0
  45. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  46. package/dest/l1_tx_utils/index.js +10 -0
  47. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  48. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  49. package/dest/l1_tx_utils/interfaces.js +4 -0
  50. package/dest/l1_tx_utils/l1_tx_utils.d.ts +95 -0
  51. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  52. package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
  53. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  54. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  55. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  56. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +81 -0
  57. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  58. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +294 -0
  59. package/dest/l1_tx_utils/signer.d.ts +4 -0
  60. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  61. package/dest/l1_tx_utils/signer.js +16 -0
  62. package/dest/l1_tx_utils/types.d.ts +67 -0
  63. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  64. package/dest/l1_tx_utils/types.js +26 -0
  65. package/dest/l1_tx_utils/utils.d.ts +4 -0
  66. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  67. package/dest/l1_tx_utils/utils.js +14 -0
  68. package/dest/publisher_manager.d.ts +7 -2
  69. package/dest/publisher_manager.d.ts.map +1 -1
  70. package/dest/publisher_manager.js +36 -8
  71. package/dest/queries.d.ts.map +1 -1
  72. package/dest/queries.js +8 -2
  73. package/dest/test/chain_monitor.js +2 -2
  74. package/dest/test/delayed_tx_utils.d.ts +2 -2
  75. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  76. package/dest/test/delayed_tx_utils.js +2 -2
  77. package/dest/test/eth_cheat_codes.d.ts +18 -1
  78. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  79. package/dest/test/eth_cheat_codes.js +101 -22
  80. package/dest/test/rollup_cheat_codes.d.ts +3 -1
  81. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  82. package/dest/test/rollup_cheat_codes.js +2 -2
  83. package/dest/test/upgrade_utils.js +1 -1
  84. package/package.json +6 -6
  85. package/src/config.ts +34 -44
  86. package/src/contracts/empire_base.ts +1 -1
  87. package/src/contracts/empire_slashing_proposer.ts +1 -1
  88. package/src/contracts/fee_asset_handler.ts +1 -1
  89. package/src/contracts/governance.ts +3 -3
  90. package/src/contracts/governance_proposer.ts +3 -4
  91. package/src/contracts/multicall.ts +12 -10
  92. package/src/contracts/rollup.ts +31 -86
  93. package/src/contracts/slasher_contract.ts +22 -0
  94. package/src/deploy_l1_contracts.ts +351 -178
  95. package/src/index.ts +1 -1
  96. package/src/l1_artifacts.ts +8 -0
  97. package/src/l1_contract_addresses.ts +3 -1
  98. package/src/l1_tx_utils/README.md +177 -0
  99. package/src/l1_tx_utils/config.ts +140 -0
  100. package/src/l1_tx_utils/constants.ts +18 -0
  101. package/src/l1_tx_utils/factory.ts +64 -0
  102. package/src/l1_tx_utils/index.ts +12 -0
  103. package/src/l1_tx_utils/interfaces.ts +86 -0
  104. package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
  105. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  106. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +372 -0
  107. package/src/l1_tx_utils/signer.ts +28 -0
  108. package/src/l1_tx_utils/types.ts +85 -0
  109. package/src/l1_tx_utils/utils.ts +16 -0
  110. package/src/publisher_manager.ts +51 -9
  111. package/src/queries.ts +10 -1
  112. package/src/test/chain_monitor.ts +2 -2
  113. package/src/test/delayed_tx_utils.ts +2 -2
  114. package/src/test/eth_cheat_codes.ts +120 -20
  115. package/src/test/rollup_cheat_codes.ts +5 -2
  116. package/src/test/upgrade_utils.ts +1 -1
  117. package/dest/l1_tx_utils.d.ts +0 -252
  118. package/dest/l1_tx_utils.d.ts.map +0 -1
  119. package/dest/l1_tx_utils.js +0 -834
  120. package/dest/l1_tx_utils_with_blobs.d.ts +0 -20
  121. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  122. package/dest/l1_tx_utils_with_blobs.js +0 -87
  123. package/src/l1_tx_utils.ts +0 -1124
  124. package/src/l1_tx_utils_with_blobs.ts +0 -150
@@ -6,10 +6,11 @@ import type { Fr } from '@aztec/foundation/fields';
6
6
  import { jsonStringify } from '@aztec/foundation/json-rpc';
7
7
  import { type Logger, createLogger } from '@aztec/foundation/log';
8
8
  import { DateProvider } from '@aztec/foundation/timer';
9
- import type { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
9
+ import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
10
10
 
11
11
  import type { Abi, Narrow } from 'abitype';
12
12
  import { mkdir, writeFile } from 'fs/promises';
13
+ import chunk from 'lodash.chunk';
13
14
  import {
14
15
  type Chain,
15
16
  type ContractConstructorArgs,
@@ -33,7 +34,6 @@ import { createExtendedL1Client } from './client.js';
33
34
  import {
34
35
  type L1ContractsConfig,
35
36
  getEntryQueueConfig,
36
- getGSEConfiguration,
37
37
  getGovernanceConfiguration,
38
38
  getRewardBoostConfig,
39
39
  getRewardConfig,
@@ -45,6 +45,7 @@ import { RegistryContract } from './contracts/registry.js';
45
45
  import { RollupContract, SlashingProposerType } from './contracts/rollup.js';
46
46
  import {
47
47
  CoinIssuerArtifact,
48
+ DateGatedRelayerArtifact,
48
49
  FeeAssetArtifact,
49
50
  FeeAssetHandlerArtifact,
50
51
  GSEArtifact,
@@ -63,21 +64,19 @@ import {
63
64
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
64
65
  import {
65
66
  type GasPrice,
66
- type L1GasConfig,
67
+ type L1TxConfig,
67
68
  type L1TxRequest,
68
69
  L1TxUtils,
69
70
  type L1TxUtilsConfig,
70
71
  createL1TxUtilsFromViemWallet,
71
72
  getL1TxUtilsConfigEnvVars,
72
- } from './l1_tx_utils.js';
73
+ } from './l1_tx_utils/index.js';
73
74
  import type { ExtendedViemWalletClient } from './types.js';
74
75
  import { formatViemError } from './utils.js';
75
76
  import { ZK_PASSPORT_DOMAIN, ZK_PASSPORT_SCOPE, ZK_PASSPORT_VERIFIER_ADDRESS } from './zkPassportVerifierAddress.js';
76
77
 
77
78
  export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
78
79
 
79
- const networkName = getActiveNetworkName();
80
-
81
80
  export type Operator = {
82
81
  attester: EthAddress;
83
82
  withdrawer: EthAddress;
@@ -166,6 +165,8 @@ export interface DeployL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1T
166
165
  realVerifier: boolean;
167
166
  /** The zk passport args */
168
167
  zkPassportArgs?: ZKPassportArgs;
168
+ /** If provided, use this token for BOTH fee and staking assets (skip deployments) */
169
+ existingTokenAddress?: EthAddress;
169
170
  }
170
171
 
171
172
  export interface ZKPassportArgs {
@@ -177,39 +178,126 @@ export interface ZKPassportArgs {
177
178
  zkPassportScope?: string;
178
179
  }
179
180
 
181
+ // Minimal ERC20 ABI for validation purposes. We only read view methods.
182
+ const ERC20_VALIDATION_ABI = [
183
+ {
184
+ type: 'function',
185
+ name: 'totalSupply',
186
+ stateMutability: 'view',
187
+ inputs: [],
188
+ outputs: [{ name: '', type: 'uint256' }],
189
+ },
190
+ {
191
+ type: 'function',
192
+ name: 'name',
193
+ stateMutability: 'view',
194
+ inputs: [],
195
+ outputs: [{ name: '', type: 'string' }],
196
+ },
197
+ {
198
+ type: 'function',
199
+ name: 'symbol',
200
+ stateMutability: 'view',
201
+ inputs: [],
202
+ outputs: [{ name: '', type: 'string' }],
203
+ },
204
+ {
205
+ type: 'function',
206
+ name: 'decimals',
207
+ stateMutability: 'view',
208
+ inputs: [],
209
+ outputs: [{ name: '', type: 'uint8' }],
210
+ },
211
+ ] as const;
212
+
213
+ /**
214
+ * Validates that the provided address points to a contract that resembles an ERC20 token.
215
+ * Checks for contract code and attempts common ERC20 view calls.
216
+ * Throws an error if validation fails.
217
+ */
218
+ export async function validateExistingErc20TokenAddress(
219
+ l1Client: ExtendedViemWalletClient,
220
+ tokenAddress: EthAddress,
221
+ logger: Logger,
222
+ ): Promise<void> {
223
+ const addressString = tokenAddress.toString();
224
+
225
+ // Ensure there is contract code at the address
226
+ const code = await l1Client.getCode({ address: addressString });
227
+ if (!code || code === '0x') {
228
+ throw new Error(`No contract code found at provided token address ${addressString}`);
229
+ }
230
+
231
+ const contract = getContract({
232
+ address: getAddress(addressString),
233
+ abi: ERC20_VALIDATION_ABI,
234
+ client: l1Client,
235
+ });
236
+
237
+ // Validate all required ERC20 methods in parallel
238
+ const checks = [
239
+ contract.read.totalSupply().then(total => typeof total === 'bigint'),
240
+ contract.read.name().then(() => true),
241
+ contract.read.symbol().then(() => true),
242
+ contract.read.decimals().then(dec => typeof dec === 'number' || typeof dec === 'bigint'),
243
+ ];
244
+
245
+ const results = await Promise.allSettled(checks);
246
+ const failedChecks = results.filter(result => result.status === 'rejected' || result.value !== true);
247
+
248
+ if (failedChecks.length > 0) {
249
+ throw new Error(`Address ${addressString} does not appear to implement ERC20 view methods`);
250
+ }
251
+
252
+ logger.verbose(`Validated existing token at ${addressString} appears to be ERC20-compatible`);
253
+ }
254
+
180
255
  export const deploySharedContracts = async (
181
256
  l1Client: ExtendedViemWalletClient,
182
257
  deployer: L1Deployer,
183
258
  args: DeployL1ContractsArgs,
184
259
  logger: Logger,
185
260
  ) => {
261
+ const networkName = getActiveNetworkName();
262
+
186
263
  logger.info(`Deploying shared contracts for network configuration: ${networkName}`);
187
264
 
188
265
  const txHashes: Hex[] = [];
189
266
 
190
- const feeAssetAddress = await deployer.deploy(FeeAssetArtifact, ['FeeJuice', 'FEE', l1Client.account.address]);
191
- logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
192
-
193
- const stakingAssetAddress = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
194
- logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
267
+ let feeAssetAddress: EthAddress;
268
+ let stakingAssetAddress: EthAddress;
269
+ if (args.existingTokenAddress) {
270
+ await validateExistingErc20TokenAddress(l1Client, args.existingTokenAddress, logger);
271
+ feeAssetAddress = args.existingTokenAddress;
272
+ stakingAssetAddress = args.existingTokenAddress;
273
+ logger.verbose(`Using existing token for fee and staking assets at ${args.existingTokenAddress}`);
274
+ } else {
275
+ const deployedFee = await deployer.deploy(FeeAssetArtifact, ['FeeJuice', 'FEE', l1Client.account.address]);
276
+ feeAssetAddress = deployedFee.address;
277
+ logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
195
278
 
196
- const gseConfiguration = getGSEConfiguration(networkName);
279
+ const deployedStaking = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
280
+ stakingAssetAddress = deployedStaking.address;
281
+ logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
282
+ }
197
283
 
198
- const gseAddress = await deployer.deploy(GSEArtifact, [
199
- l1Client.account.address,
200
- stakingAssetAddress.toString(),
201
- gseConfiguration.activationThreshold,
202
- gseConfiguration.ejectionThreshold,
203
- ]);
284
+ const gseAddress = (
285
+ await deployer.deploy(GSEArtifact, [
286
+ l1Client.account.address,
287
+ stakingAssetAddress.toString(),
288
+ args.activationThreshold,
289
+ args.ejectionThreshold,
290
+ ])
291
+ ).address;
204
292
  logger.verbose(`Deployed GSE at ${gseAddress}`);
205
293
 
206
- const registryAddress = await deployer.deploy(RegistryArtifact, [
294
+ const { address: registryAddress } = await deployer.deploy(RegistryArtifact, [
207
295
  l1Client.account.address,
208
296
  feeAssetAddress.toString(),
209
297
  ]);
210
298
  logger.verbose(`Deployed Registry at ${registryAddress}`);
211
299
 
212
- const governanceProposerAddress = await deployer.deploy(GovernanceProposerArtifact, [
300
+ const { address: governanceProposerAddress } = await deployer.deploy(GovernanceProposerArtifact, [
213
301
  registryAddress.toString(),
214
302
  gseAddress.toString(),
215
303
  BigInt(args.governanceProposerQuorum ?? args.governanceProposerRoundSize / 2 + 1),
@@ -219,7 +307,7 @@ export const deploySharedContracts = async (
219
307
 
220
308
  // @note @LHerskind the assets are expected to be the same at some point, but for better
221
309
  // configurability they are different for now.
222
- const governanceAddress = await deployer.deploy(GovernanceArtifact, [
310
+ const { address: governanceAddress } = await deployer.deploy(GovernanceArtifact, [
223
311
  stakingAssetAddress.toString(),
224
312
  governanceProposerAddress.toString(),
225
313
  gseAddress.toString(),
@@ -261,11 +349,13 @@ export const deploySharedContracts = async (
261
349
  txHashes.push(txHash);
262
350
  }
263
351
 
264
- const coinIssuerAddress = await deployer.deploy(CoinIssuerArtifact, [
265
- feeAssetAddress.toString(),
266
- 1_000_000n * 10n ** 18n, // @todo #8084
267
- l1Client.account.address,
268
- ]);
352
+ const coinIssuerAddress = (
353
+ await deployer.deploy(CoinIssuerArtifact, [
354
+ feeAssetAddress.toString(),
355
+ (25_000_000_000n * 10n ** 18n) / (60n * 60n * 24n * 365n),
356
+ l1Client.account.address,
357
+ ])
358
+ ).address;
269
359
  logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
270
360
 
271
361
  logger.verbose(`Waiting for deployments to complete`);
@@ -277,17 +367,19 @@ export const deploySharedContracts = async (
277
367
  let stakingAssetHandlerAddress: EthAddress | undefined = undefined;
278
368
  let zkPassportVerifierAddress: EthAddress | undefined = undefined;
279
369
 
280
- // Only if not on mainnet will we deploy the handlers
281
- if (l1Client.chain.id !== 1) {
370
+ // Only if not on mainnet will we deploy the handlers, and only when we control the token
371
+ if (l1Client.chain.id !== 1 && !args.existingTokenAddress) {
282
372
  /* -------------------------------------------------------------------------- */
283
373
  /* CHEAT CODES START HERE */
284
374
  /* -------------------------------------------------------------------------- */
285
375
 
286
- feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
287
- l1Client.account.address,
288
- feeAssetAddress.toString(),
289
- BigInt(1000n * 10n ** 18n),
290
- ]);
376
+ feeAssetHandlerAddress = (
377
+ await deployer.deploy(FeeAssetHandlerArtifact, [
378
+ l1Client.account.address,
379
+ feeAssetAddress.toString(),
380
+ BigInt(1000n * 10n ** 18n),
381
+ ])
382
+ ).address;
291
383
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
292
384
 
293
385
  // Only if we are "fresh" will we be adding as a minter, otherwise above will simply get same address
@@ -316,6 +408,7 @@ export const deploySharedContracts = async (
316
408
  stakingAsset: stakingAssetAddress.toString(),
317
409
  registry: registryAddress.toString(),
318
410
  withdrawer: AMIN.toString(),
411
+ validatorsToFlush: 16n,
319
412
  mintInterval: BigInt(60 * 60 * 24),
320
413
  depositsPerMint: BigInt(10),
321
414
  depositMerkleRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
@@ -329,7 +422,8 @@ export const deploySharedContracts = async (
329
422
  skipMerkleCheck: true, // skip merkle check - needed for testing without generating proofs
330
423
  } as const;
331
424
 
332
- stakingAssetHandlerAddress = await deployer.deploy(StakingAssetHandlerArtifact, [stakingAssetHandlerDeployArgs]);
425
+ stakingAssetHandlerAddress = (await deployer.deploy(StakingAssetHandlerArtifact, [stakingAssetHandlerDeployArgs]))
426
+ .address;
333
427
  logger.verbose(`Deployed StakingAssetHandler at ${stakingAssetHandlerAddress}`);
334
428
 
335
429
  const { txHash: stakingMinterTxHash } = await deployer.sendTransaction({
@@ -365,19 +459,23 @@ export const deploySharedContracts = async (
365
459
 
366
460
  const rewardDistributorAddress = await registry.getRewardDistributor();
367
461
 
368
- const blockReward = getRewardConfig(networkName).blockReward;
462
+ if (!args.existingTokenAddress) {
463
+ const blockReward = getRewardConfig(networkName).blockReward;
369
464
 
370
- const funding = blockReward * 200000n;
371
- const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
372
- to: feeAssetAddress.toString(),
373
- data: encodeFunctionData({
374
- abi: FeeAssetArtifact.contractAbi,
375
- functionName: 'mint',
376
- args: [rewardDistributorAddress.toString(), funding],
377
- }),
378
- });
465
+ const funding = blockReward * 200000n;
466
+ const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
467
+ to: feeAssetAddress.toString(),
468
+ data: encodeFunctionData({
469
+ abi: FeeAssetArtifact.contractAbi,
470
+ functionName: 'mint',
471
+ args: [rewardDistributorAddress.toString(), funding],
472
+ }),
473
+ });
379
474
 
380
- logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
475
+ logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
476
+ } else {
477
+ logger.verbose(`Skipping reward distributor funding as existing token is provided`);
478
+ }
381
479
 
382
480
  /* -------------------------------------------------------------------------- */
383
481
  /* FUND REWARD DISTRIBUTOR STOP */
@@ -400,7 +498,7 @@ export const deploySharedContracts = async (
400
498
 
401
499
  const getZkPassportVerifierAddress = async (deployer: L1Deployer, args: DeployL1ContractsArgs): Promise<EthAddress> => {
402
500
  if (args.zkPassportArgs?.mockZkPassportVerifier) {
403
- return await deployer.deploy(mockVerifiers.mockZkPassportVerifier);
501
+ return (await deployer.deploy(mockVerifiers.mockZkPassportVerifier)).address;
404
502
  }
405
503
  return ZK_PASSPORT_VERIFIER_ADDRESS;
406
504
  };
@@ -453,7 +551,7 @@ export const deployRollupForUpgrade = async (
453
551
  };
454
552
 
455
553
  export const deploySlashFactory = async (deployer: L1Deployer, rollupAddress: Hex, logger: Logger) => {
456
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [rollupAddress]);
554
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress])).address;
457
555
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
458
556
  return slashFactoryAddress;
459
557
  };
@@ -462,10 +560,12 @@ export const deployUpgradePayload = async (
462
560
  deployer: L1Deployer,
463
561
  addresses: Pick<L1ContractAddresses, 'registryAddress' | 'rollupAddress'>,
464
562
  ) => {
465
- const payloadAddress = await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
466
- addresses.registryAddress.toString(),
467
- addresses.rollupAddress.toString(),
468
- ]);
563
+ const payloadAddress = (
564
+ await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
565
+ addresses.registryAddress.toString(),
566
+ addresses.rollupAddress.toString(),
567
+ ])
568
+ ).address;
469
569
 
470
570
  return payloadAddress;
471
571
  };
@@ -509,6 +609,7 @@ export const deployRollup = async (
509
609
  if (!addresses.gseAddress) {
510
610
  throw new Error('GSE address is required when deploying');
511
611
  }
612
+ const networkName = getActiveNetworkName();
512
613
 
513
614
  logger.info(`Deploying rollup using network configuration: ${networkName}`);
514
615
 
@@ -517,10 +618,10 @@ export const deployRollup = async (
517
618
  let epochProofVerifier = EthAddress.ZERO;
518
619
 
519
620
  if (args.realVerifier) {
520
- epochProofVerifier = await deployer.deploy(l1ArtifactsVerifiers.honkVerifier);
621
+ epochProofVerifier = (await deployer.deploy(l1ArtifactsVerifiers.honkVerifier)).address;
521
622
  logger.verbose(`Rollup will use the real verifier at ${epochProofVerifier}`);
522
623
  } else {
523
- epochProofVerifier = await deployer.deploy(mockVerifiers.mockVerifier);
624
+ epochProofVerifier = (await deployer.deploy(mockVerifiers.mockVerifier)).address;
524
625
  logger.verbose(`Rollup will use the mock verifier at ${epochProofVerifier}`);
525
626
  }
526
627
 
@@ -533,6 +634,7 @@ export const deployRollup = async (
533
634
  aztecSlotDuration: BigInt(args.aztecSlotDuration),
534
635
  aztecEpochDuration: BigInt(args.aztecEpochDuration),
535
636
  targetCommitteeSize: BigInt(args.aztecTargetCommitteeSize),
637
+ lagInEpochs: BigInt(args.lagInEpochs),
536
638
  aztecProofSubmissionEpochs: BigInt(args.aztecProofSubmissionEpochs),
537
639
  slashingQuorum: BigInt(args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1),
538
640
  slashingRoundSize: BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration),
@@ -549,6 +651,8 @@ export const deployRollup = async (
549
651
  slasherFlavor: slasherFlavorToSolidityEnum(args.slasherFlavor),
550
652
  slashingOffsetInRounds: BigInt(args.slashingOffsetInRounds),
551
653
  slashAmounts: [args.slashAmountSmall, args.slashAmountMedium, args.slashAmountLarge],
654
+ localEjectionThreshold: args.localEjectionThreshold,
655
+ slashingDisableDuration: BigInt(args.slashingDisableDuration ?? 0n),
552
656
  };
553
657
 
554
658
  const genesisStateArgs = {
@@ -579,8 +683,10 @@ export const deployRollup = async (
579
683
  rollupConfigArgs,
580
684
  ] as const;
581
685
 
582
- const rollupAddress = await deployer.deploy(RollupArtifact, rollupArgs, { gasLimit: 15_000_000n });
583
- logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
686
+ const { address: rollupAddress, existed: rollupExisted } = await deployer.deploy(RollupArtifact, rollupArgs, {
687
+ gasLimit: 15_000_000n,
688
+ });
689
+ logger.verbose(`Deployed Rollup at ${rollupAddress}, already existed: ${rollupExisted}`, rollupConfigArgs);
584
690
 
585
691
  const rollupContract = new RollupContract(extendedClient, rollupAddress);
586
692
 
@@ -588,24 +694,29 @@ export const deployRollup = async (
588
694
  logger.verbose(`All core contracts have been deployed`);
589
695
 
590
696
  if (args.feeJuicePortalInitialBalance && args.feeJuicePortalInitialBalance > 0n) {
591
- const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
697
+ // Skip funding when using an external token, as we likely don't have mint permissions
698
+ if (!('existingTokenAddress' in args) || !args.existingTokenAddress) {
699
+ const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
592
700
 
593
- // In fast mode, use the L1TxUtils to send transactions with nonce management
594
- const { txHash: mintTxHash } = await deployer.sendTransaction({
595
- to: addresses.feeJuiceAddress.toString(),
596
- data: encodeFunctionData({
597
- abi: FeeAssetArtifact.contractAbi,
598
- functionName: 'mint',
599
- args: [feeJuicePortalAddress.toString(), args.feeJuicePortalInitialBalance],
600
- }),
601
- });
602
- logger.verbose(
603
- `Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`,
604
- );
605
- txHashes.push(mintTxHash);
701
+ // In fast mode, use the L1TxUtils to send transactions with nonce management
702
+ const { txHash: mintTxHash } = await deployer.sendTransaction({
703
+ to: addresses.feeJuiceAddress.toString(),
704
+ data: encodeFunctionData({
705
+ abi: FeeAssetArtifact.contractAbi,
706
+ functionName: 'mint',
707
+ args: [feeJuicePortalAddress.toString(), args.feeJuicePortalInitialBalance],
708
+ }),
709
+ });
710
+ logger.verbose(
711
+ `Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`,
712
+ );
713
+ txHashes.push(mintTxHash);
714
+ } else {
715
+ logger.verbose('Skipping fee juice portal funding due to external token usage');
716
+ }
606
717
  }
607
718
 
608
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [rollupAddress.toString()]);
719
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress.toString()])).address;
609
720
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
610
721
 
611
722
  // We need to call a function on the registry to set the various contract addresses.
@@ -667,7 +778,17 @@ export const deployRollup = async (
667
778
  logger.verbose(`Not the owner of the gse, skipping rollup addition`);
668
779
  }
669
780
 
670
- if (args.initialValidators && (await gseContract.read.isRollupRegistered([rollupContract.address]))) {
781
+ const activeAttestorCount = await rollupContract.getActiveAttesterCount();
782
+ const queuedAttestorCount = await rollupContract.getEntryQueueLength();
783
+ logger.info(`Rollup has ${activeAttestorCount} active attestors and ${queuedAttestorCount} queued attestors`);
784
+
785
+ const shouldAddValidators = activeAttestorCount === 0n && queuedAttestorCount === 0n;
786
+
787
+ if (
788
+ args.initialValidators &&
789
+ shouldAddValidators &&
790
+ (await gseContract.read.isRollupRegistered([rollupContract.address]))
791
+ ) {
671
792
  await addMultipleValidators(
672
793
  extendedClient,
673
794
  deployer,
@@ -715,6 +836,7 @@ export const handoverToGovernance = async (
715
836
  governanceAddress: EthAddress,
716
837
  logger: Logger,
717
838
  acceleratedTestDeployments: boolean | undefined,
839
+ useExternalToken: boolean = false,
718
840
  ) => {
719
841
  // We need to call a function on the registry to set the various contract addresses.
720
842
  const registryContract = getContract({
@@ -780,7 +902,10 @@ export const handoverToGovernance = async (
780
902
  txHashes.push(transferOwnershipTxHash);
781
903
  }
782
904
 
783
- if (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString()) {
905
+ if (
906
+ !useExternalToken &&
907
+ (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString())
908
+ ) {
784
909
  const { txHash } = await deployer.sendTransaction(
785
910
  {
786
911
  to: feeAssetAddress.toString(),
@@ -807,23 +932,28 @@ export const handoverToGovernance = async (
807
932
  );
808
933
  logger.verbose(`Accept ownership of fee asset in ${acceptTokenOwnershipTxHash}`);
809
934
  txHashes.push(acceptTokenOwnershipTxHash);
935
+ } else if (useExternalToken) {
936
+ logger.verbose('Skipping fee asset ownership transfer due to external token usage');
810
937
  }
811
938
 
939
+ // Either deploy or at least predict the address of the date gated relayer
940
+ const dateGatedRelayer = await deployer.deploy(DateGatedRelayerArtifact, [
941
+ governanceAddress.toString(),
942
+ 1798761600n, // 2027-01-01 00:00:00 UTC
943
+ ]);
944
+
812
945
  // If the owner is not the Governance contract, transfer ownership to the Governance contract
813
- if (
814
- acceleratedTestDeployments ||
815
- (await coinIssuerContract.read.owner()) !== getAddress(governanceAddress.toString())
816
- ) {
946
+ if (acceleratedTestDeployments || (await coinIssuerContract.read.owner()) === deployer.client.account.address) {
817
947
  const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
818
948
  to: coinIssuerContract.address,
819
949
  data: encodeFunctionData({
820
950
  abi: CoinIssuerArtifact.contractAbi,
821
951
  functionName: 'transferOwnership',
822
- args: [getAddress(governanceAddress.toString())],
952
+ args: [getAddress(dateGatedRelayer.address.toString())],
823
953
  }),
824
954
  });
825
955
  logger.verbose(
826
- `Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
956
+ `Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the DateGatedRelayer ${dateGatedRelayer.address} in tx ${transferOwnershipTxHash}`,
827
957
  );
828
958
  txHashes.push(transferOwnershipTxHash);
829
959
  }
@@ -831,6 +961,8 @@ export const handoverToGovernance = async (
831
961
  // Wait for all actions to be mined
832
962
  await deployer.waitForDeployments();
833
963
  await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
964
+
965
+ return { dateGatedRelayerAddress: dateGatedRelayer.address };
834
966
  };
835
967
 
836
968
  /*
@@ -877,100 +1009,112 @@ export const addMultipleValidators = async (
877
1009
  validators = enrichedValidators.filter(v => v.status === 0).map(v => v.operator);
878
1010
  }
879
1011
 
880
- if (validators.length > 0) {
881
- const gseContract = new GSEContract(extendedClient, gseAddress);
882
- const multiAdder = await deployer.deploy(MultiAdderArtifact, [rollupAddress, deployer.client.account.address]);
883
-
884
- const makeValidatorTuples = async (validator: Operator) => {
885
- const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
886
- return {
887
- attester: getAddress(validator.attester.toString()),
888
- withdrawer: getAddress(validator.withdrawer.toString()),
889
- ...registrationTuple,
890
- };
1012
+ if (validators.length === 0) {
1013
+ logger.warn('No validators to add. Skipping.');
1014
+ return;
1015
+ }
1016
+
1017
+ const gseContract = new GSEContract(extendedClient, gseAddress);
1018
+ const multiAdder = (await deployer.deploy(MultiAdderArtifact, [rollupAddress, deployer.client.account.address]))
1019
+ .address;
1020
+
1021
+ const makeValidatorTuples = async (validator: Operator) => {
1022
+ const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
1023
+ return {
1024
+ attester: getAddress(validator.attester.toString()),
1025
+ withdrawer: getAddress(validator.withdrawer.toString()),
1026
+ ...registrationTuple,
891
1027
  };
1028
+ };
892
1029
 
893
- const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
1030
+ const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
894
1031
 
895
- // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
896
- const stakeNeeded = activationThreshold * BigInt(validators.length);
1032
+ // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
1033
+ const stakeNeeded = activationThreshold * BigInt(validators.length);
897
1034
 
898
- await deployer.l1TxUtils.sendAndMonitorTransaction({
899
- to: stakingAssetAddress,
900
- data: encodeFunctionData({
901
- abi: StakingAssetArtifact.contractAbi,
902
- functionName: 'mint',
903
- args: [multiAdder.toString(), stakeNeeded],
904
- }),
905
- });
1035
+ await deployer.l1TxUtils.sendAndMonitorTransaction({
1036
+ to: stakingAssetAddress,
1037
+ data: encodeFunctionData({
1038
+ abi: StakingAssetArtifact.contractAbi,
1039
+ functionName: 'mint',
1040
+ args: [multiAdder.toString(), stakeNeeded],
1041
+ }),
1042
+ });
906
1043
 
907
- const entryQueueLengthBefore = await rollup.getEntryQueueLength();
908
- const validatorCountBefore = await rollup.getActiveAttesterCount();
909
-
910
- logger.info(`Adding ${validators.length} validators to the rollup`);
911
-
912
- // Adding to the queue and flushing need to be done in two transactions
913
- // if we are adding many validators.
914
- if (validatorsTuples.length > 10) {
915
- await deployer.l1TxUtils.sendAndMonitorTransaction(
916
- {
917
- to: multiAdder.toString(),
918
- data: encodeFunctionData({
919
- abi: MultiAdderArtifact.contractAbi,
920
- functionName: 'addValidators',
921
- args: [validatorsTuples, true],
922
- }),
923
- },
924
- {
925
- gasLimit: 40_000_000n,
926
- },
927
- );
1044
+ const entryQueueLengthBefore = await rollup.getEntryQueueLength();
1045
+ const validatorCountBefore = await rollup.getActiveAttesterCount();
928
1046
 
929
- await deployer.l1TxUtils.sendAndMonitorTransaction(
930
- {
931
- to: rollupAddress,
932
- data: encodeFunctionData({
933
- abi: RollupArtifact.contractAbi,
934
- functionName: 'flushEntryQueue',
935
- args: [],
936
- }),
937
- },
938
- {
939
- gasLimit: 40_000_000n,
940
- },
941
- );
942
- } else {
943
- await deployer.l1TxUtils.sendAndMonitorTransaction(
944
- {
945
- to: multiAdder.toString(),
946
- data: encodeFunctionData({
947
- abi: MultiAdderArtifact.contractAbi,
948
- functionName: 'addValidators',
949
- args: [validatorsTuples, false],
950
- }),
951
- },
952
- {
953
- gasLimit: 45_000_000n,
954
- },
955
- );
956
- }
1047
+ logger.info(`Adding ${validators.length} validators to the rollup`);
957
1048
 
958
- const entryQueueLengthAfter = await rollup.getEntryQueueLength();
959
- const validatorCountAfter = await rollup.getActiveAttesterCount();
1049
+ const chunkSize = 16;
960
1050
 
961
- if (
962
- entryQueueLengthAfter + validatorCountAfter <
963
- entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)
964
- ) {
965
- throw new Error(
966
- `Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
967
- );
1051
+ // We will add `chunkSize` validators to the queue until we have covered all of our validators.
1052
+ // The `chunkSize` needs to be small enough to fit inside a single tx, therefore 16.
1053
+ for (const c of chunk(validatorsTuples, chunkSize)) {
1054
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1055
+ {
1056
+ to: multiAdder.toString(),
1057
+ data: encodeFunctionData({
1058
+ abi: MultiAdderArtifact.contractAbi,
1059
+ functionName: 'addValidators',
1060
+ args: [c, BigInt(chunkSize)],
1061
+ }),
1062
+ },
1063
+ {
1064
+ gasLimit: 16_000_000n,
1065
+ },
1066
+ );
1067
+ }
1068
+
1069
+ // After adding to the queue, we will now try to flush from it.
1070
+ // We are explicitly doing this as a second step instead of as part of adding to benefit
1071
+ // from the accounting used to speed the process up.
1072
+ // As the queue computes the amount of possible flushes in an epoch when told to flush,
1073
+ // waiting until we have added all we want allows us to benefit in the case were we added
1074
+ // enough to pass the bootstrap set size without needing to wait another epoch.
1075
+ // This is useful when we are testing as it speeds up the tests slightly.
1076
+ while (true) {
1077
+ // If the queue is empty, we can break
1078
+ if ((await rollup.getEntryQueueLength()) == 0n) {
1079
+ break;
1080
+ }
1081
+
1082
+ // If there are no available validator flushes, no need to even try
1083
+ if ((await rollup.getAvailableValidatorFlushes()) == 0n) {
1084
+ break;
968
1085
  }
969
1086
 
970
- logger.info(
971
- `Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
1087
+ // Note that we are flushing at most `chunkSize` at each call
1088
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1089
+ {
1090
+ to: rollup.address,
1091
+ data: encodeFunctionData({
1092
+ abi: RollupArtifact.contractAbi,
1093
+ functionName: 'flushEntryQueue',
1094
+ args: [BigInt(chunkSize)],
1095
+ }),
1096
+ },
1097
+ {
1098
+ gasLimit: 16_000_000n,
1099
+ },
972
1100
  );
973
1101
  }
1102
+
1103
+ const entryQueueLengthAfter = await rollup.getEntryQueueLength();
1104
+ const validatorCountAfter = await rollup.getActiveAttesterCount();
1105
+
1106
+ if (
1107
+ entryQueueLengthAfter + validatorCountAfter <
1108
+ entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)
1109
+ ) {
1110
+ throw new Error(
1111
+ `Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}. A likely issue is the bootstrap size.`,
1112
+ );
1113
+ }
1114
+
1115
+ logger.info(
1116
+ `Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
1117
+ );
974
1118
  }
975
1119
  };
976
1120
 
@@ -993,11 +1137,13 @@ export const cheat_initializeFeeAssetHandler = async (
993
1137
  feeAssetHandlerAddress: EthAddress;
994
1138
  txHash: Hex;
995
1139
  }> => {
996
- const feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
997
- extendedClient.account.address,
998
- feeAssetAddress.toString(),
999
- BigInt(1e18),
1000
- ]);
1140
+ const feeAssetHandlerAddress = (
1141
+ await deployer.deploy(FeeAssetHandlerArtifact, [
1142
+ extendedClient.account.address,
1143
+ feeAssetAddress.toString(),
1144
+ BigInt(1e18),
1145
+ ])
1146
+ ).address;
1001
1147
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
1002
1148
 
1003
1149
  const { txHash } = await deployer.sendTransaction({
@@ -1033,6 +1179,13 @@ export const deployL1Contracts = async (
1033
1179
  logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
1034
1180
  validateConfig(args);
1035
1181
 
1182
+ if (args.initialValidators && args.initialValidators.length > 0 && args.existingTokenAddress) {
1183
+ throw new Error(
1184
+ 'Cannot deploy with both initialValidators and existingTokenAddress. ' +
1185
+ 'Initial validator funding requires minting tokens, which is not possible with an external token.',
1186
+ );
1187
+ }
1188
+
1036
1189
  const l1Client = createExtendedL1Client(rpcUrls, account, chain);
1037
1190
 
1038
1191
  // Deploy multicall3 if it does not exist in this network
@@ -1102,7 +1255,7 @@ export const deployL1Contracts = async (
1102
1255
  await deployer.waitForDeployments();
1103
1256
 
1104
1257
  // Now that the rollup has been deployed and added to the registry, transfer ownership to governance
1105
- await handoverToGovernance(
1258
+ const { dateGatedRelayerAddress } = await handoverToGovernance(
1106
1259
  l1Client,
1107
1260
  deployer,
1108
1261
  registryAddress,
@@ -1112,6 +1265,7 @@ export const deployL1Contracts = async (
1112
1265
  governanceAddress,
1113
1266
  logger,
1114
1267
  args.acceleratedTestDeployments,
1268
+ !!args.existingTokenAddress,
1115
1269
  );
1116
1270
 
1117
1271
  logger.info(`Handing over to governance complete`);
@@ -1250,6 +1404,7 @@ export const deployL1Contracts = async (
1250
1404
  // Ensure the verification output directory exists
1251
1405
  await mkdir(createVerificationJson, { recursive: true });
1252
1406
  const verificationOutputPath = `${createVerificationJson}/l1-verify-${chain.id}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
1407
+ const networkName = getActiveNetworkName();
1253
1408
  const verificationData = {
1254
1409
  chainId: chain.id,
1255
1410
  network: networkName,
@@ -1295,6 +1450,7 @@ export const deployL1Contracts = async (
1295
1450
  stakingAssetHandlerAddress,
1296
1451
  zkPassportVerifierAddress,
1297
1452
  coinIssuerAddress,
1453
+ dateGatedRelayerAddress,
1298
1454
  },
1299
1455
  };
1300
1456
  };
@@ -1317,10 +1473,8 @@ export class L1Deployer {
1317
1473
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
1318
1474
  this.l1TxUtils = createL1TxUtilsFromViemWallet(
1319
1475
  this.client,
1320
- this.logger,
1321
- dateProvider,
1322
- this.txUtilsConfig,
1323
- this.acceleratedTestDeployments,
1476
+ { logger: this.logger, dateProvider },
1477
+ { ...this.txUtilsConfig, debugMaxGasLimit: acceleratedTestDeployments },
1324
1478
  );
1325
1479
  }
1326
1480
 
@@ -1328,10 +1482,10 @@ export class L1Deployer {
1328
1482
  params: ContractArtifacts<TAbi>,
1329
1483
  args?: ContractConstructorArgs<TAbi>,
1330
1484
  opts: { gasLimit?: bigint } = {},
1331
- ): Promise<EthAddress> {
1485
+ ): Promise<{ address: EthAddress; existed: boolean }> {
1332
1486
  this.logger.debug(`Deploying ${params.name} contract`, { args });
1333
1487
  try {
1334
- const { txHash, address, deployedLibraries } = await deployL1Contract(
1488
+ const { txHash, address, deployedLibraries, existed } = await deployL1Contract(
1335
1489
  this.client,
1336
1490
  params.contractAbi,
1337
1491
  params.contractBytecode,
@@ -1369,7 +1523,10 @@ export class L1Deployer {
1369
1523
  libraries: deployedLibraries ?? [],
1370
1524
  });
1371
1525
  }
1372
- return address;
1526
+ return {
1527
+ address,
1528
+ existed,
1529
+ };
1373
1530
  } catch (error) {
1374
1531
  throw new Error(`Failed to deploy ${params.name}`, { cause: formatViemError(error) });
1375
1532
  }
@@ -1397,9 +1554,13 @@ export class L1Deployer {
1397
1554
 
1398
1555
  sendTransaction(
1399
1556
  tx: L1TxRequest,
1400
- options?: L1GasConfig,
1557
+ options?: L1TxConfig,
1401
1558
  ): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
1402
- return this.l1TxUtils.sendTransaction(tx, options);
1559
+ return this.l1TxUtils.sendTransaction(tx, options).then(({ txHash, state }) => ({
1560
+ txHash,
1561
+ gasLimit: state.gasLimit,
1562
+ gasPrice: state.gasPrice,
1563
+ }));
1403
1564
  }
1404
1565
  }
1405
1566
 
@@ -1427,7 +1588,12 @@ export async function deployL1Contract(
1427
1588
  gasLimit?: bigint;
1428
1589
  acceleratedTestDeployments?: boolean;
1429
1590
  } = {},
1430
- ): Promise<{ address: EthAddress; txHash: Hex | undefined; deployedLibraries?: VerificationLibraryEntry[] }> {
1591
+ ): Promise<{
1592
+ address: EthAddress;
1593
+ txHash: Hex | undefined;
1594
+ deployedLibraries?: VerificationLibraryEntry[];
1595
+ existed: boolean;
1596
+ }> {
1431
1597
  let txHash: Hex | undefined = undefined;
1432
1598
  let resultingAddress: Hex | null | undefined = undefined;
1433
1599
  const deployedLibraries: VerificationLibraryEntry[] = [];
@@ -1437,7 +1603,11 @@ export async function deployL1Contract(
1437
1603
 
1438
1604
  if (!l1TxUtils) {
1439
1605
  const config = getL1TxUtilsConfigEnvVars();
1440
- l1TxUtils = createL1TxUtilsFromViemWallet(extendedClient, logger, undefined, config, acceleratedTestDeployments);
1606
+ l1TxUtils = createL1TxUtilsFromViemWallet(
1607
+ extendedClient,
1608
+ { logger },
1609
+ { ...config, debugMaxGasLimit: acceleratedTestDeployments },
1610
+ );
1441
1611
  }
1442
1612
 
1443
1613
  if (libraries) {
@@ -1529,6 +1699,8 @@ export async function deployL1Contract(
1529
1699
  }
1530
1700
  }
1531
1701
 
1702
+ let existed = false;
1703
+
1532
1704
  if (saltFromOpts) {
1533
1705
  logger?.info(`Deploying contract with salt ${saltFromOpts}`);
1534
1706
  const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, saltFromOpts);
@@ -1550,6 +1722,7 @@ export async function deployL1Contract(
1550
1722
  logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
1551
1723
  } else {
1552
1724
  logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
1725
+ existed = true;
1553
1726
  }
1554
1727
  } else {
1555
1728
  const deployData = encodeDeployData({ abi, bytecode, args });
@@ -1569,7 +1742,7 @@ export async function deployL1Contract(
1569
1742
  }
1570
1743
  }
1571
1744
 
1572
- return { address: EthAddress.fromString(resultingAddress!), txHash, deployedLibraries };
1745
+ return { address: EthAddress.fromString(resultingAddress!), txHash, deployedLibraries, existed };
1573
1746
  }
1574
1747
 
1575
1748
  export function getExpectedAddress(