@aztec/ethereum 3.0.0-canary.a9708bd → 3.0.0-devnet.20251212

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 (195) hide show
  1. package/dest/account.d.ts +1 -1
  2. package/dest/chain.d.ts +1 -1
  3. package/dest/client.d.ts +2 -2
  4. package/dest/client.d.ts.map +1 -1
  5. package/dest/config.d.ts +16 -8
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +160 -62
  8. package/dest/constants.d.ts +1 -1
  9. package/dest/contracts/empire_base.d.ts +7 -6
  10. package/dest/contracts/empire_base.d.ts.map +1 -1
  11. package/dest/contracts/empire_base.js +1 -1
  12. package/dest/contracts/empire_slashing_proposer.d.ts +7 -6
  13. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  14. package/dest/contracts/empire_slashing_proposer.js +9 -3
  15. package/dest/contracts/errors.d.ts +1 -1
  16. package/dest/contracts/errors.d.ts.map +1 -1
  17. package/dest/contracts/fee_asset_handler.d.ts +4 -4
  18. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  19. package/dest/contracts/fee_juice.d.ts +1 -1
  20. package/dest/contracts/fee_juice.d.ts.map +1 -1
  21. package/dest/contracts/governance.d.ts +16 -16
  22. package/dest/contracts/governance.d.ts.map +1 -1
  23. package/dest/contracts/governance.js +7 -3
  24. package/dest/contracts/governance_proposer.d.ts +6 -6
  25. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  26. package/dest/contracts/governance_proposer.js +9 -4
  27. package/dest/contracts/gse.d.ts +1 -1
  28. package/dest/contracts/gse.d.ts.map +1 -1
  29. package/dest/contracts/inbox.d.ts +1 -1
  30. package/dest/contracts/inbox.d.ts.map +1 -1
  31. package/dest/contracts/index.d.ts +1 -1
  32. package/dest/contracts/multicall.d.ts +5 -7
  33. package/dest/contracts/multicall.d.ts.map +1 -1
  34. package/dest/contracts/multicall.js +6 -4
  35. package/dest/contracts/registry.d.ts +1 -1
  36. package/dest/contracts/registry.d.ts.map +1 -1
  37. package/dest/contracts/rollup.d.ts +83 -72
  38. package/dest/contracts/rollup.d.ts.map +1 -1
  39. package/dest/contracts/rollup.js +144 -139
  40. package/dest/contracts/slasher_contract.d.ts +11 -1
  41. package/dest/contracts/slasher_contract.d.ts.map +1 -1
  42. package/dest/contracts/slasher_contract.js +18 -0
  43. package/dest/contracts/tally_slashing_proposer.d.ts +30 -9
  44. package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -1
  45. package/dest/contracts/tally_slashing_proposer.js +58 -8
  46. package/dest/contracts/utils.d.ts +1 -1
  47. package/dest/deploy_l1_contracts.d.ts +477 -15
  48. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  49. package/dest/deploy_l1_contracts.js +610 -386
  50. package/dest/eth-signer/eth-signer.d.ts +1 -1
  51. package/dest/eth-signer/index.d.ts +1 -1
  52. package/dest/forwarder_proxy.d.ts +32 -0
  53. package/dest/forwarder_proxy.d.ts.map +1 -0
  54. package/dest/forwarder_proxy.js +93 -0
  55. package/dest/l1_artifacts.d.ts +14258 -6015
  56. package/dest/l1_artifacts.d.ts.map +1 -1
  57. package/dest/l1_artifacts.js +10 -5
  58. package/dest/l1_contract_addresses.d.ts +8 -4
  59. package/dest/l1_contract_addresses.d.ts.map +1 -1
  60. package/dest/l1_contract_addresses.js +16 -26
  61. package/dest/l1_reader.d.ts +4 -2
  62. package/dest/l1_reader.d.ts.map +1 -1
  63. package/dest/l1_reader.js +14 -8
  64. package/dest/l1_tx_utils/config.d.ts +59 -0
  65. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  66. package/dest/l1_tx_utils/config.js +96 -0
  67. package/dest/l1_tx_utils/constants.d.ts +6 -0
  68. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  69. package/dest/l1_tx_utils/constants.js +14 -0
  70. package/dest/l1_tx_utils/factory.d.ts +24 -0
  71. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  72. package/dest/l1_tx_utils/factory.js +12 -0
  73. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +41 -0
  74. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -0
  75. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +48 -0
  76. package/dest/l1_tx_utils/index-blobs.d.ts +3 -0
  77. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -0
  78. package/dest/l1_tx_utils/index-blobs.js +2 -0
  79. package/dest/l1_tx_utils/index.d.ts +10 -0
  80. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  81. package/dest/l1_tx_utils/index.js +10 -0
  82. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  83. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  84. package/dest/l1_tx_utils/interfaces.js +4 -0
  85. package/dest/l1_tx_utils/l1_tx_utils.d.ts +94 -0
  86. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  87. package/dest/l1_tx_utils/l1_tx_utils.js +623 -0
  88. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  89. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  90. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  91. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +94 -0
  92. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  93. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +431 -0
  94. package/dest/l1_tx_utils/signer.d.ts +4 -0
  95. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  96. package/dest/l1_tx_utils/signer.js +16 -0
  97. package/dest/l1_tx_utils/types.d.ts +67 -0
  98. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  99. package/dest/l1_tx_utils/types.js +26 -0
  100. package/dest/l1_tx_utils/utils.d.ts +4 -0
  101. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  102. package/dest/l1_tx_utils/utils.js +14 -0
  103. package/dest/l1_types.d.ts +1 -1
  104. package/dest/publisher_manager.d.ts +8 -3
  105. package/dest/publisher_manager.d.ts.map +1 -1
  106. package/dest/publisher_manager.js +36 -8
  107. package/dest/queries.d.ts +1 -1
  108. package/dest/queries.d.ts.map +1 -1
  109. package/dest/queries.js +13 -12
  110. package/dest/test/chain_monitor.d.ts +33 -19
  111. package/dest/test/chain_monitor.d.ts.map +1 -1
  112. package/dest/test/chain_monitor.js +100 -33
  113. package/dest/test/delayed_tx_utils.d.ts +3 -3
  114. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  115. package/dest/test/delayed_tx_utils.js +2 -2
  116. package/dest/test/eth_cheat_codes.d.ts +36 -14
  117. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  118. package/dest/test/eth_cheat_codes.js +124 -31
  119. package/dest/test/eth_cheat_codes_with_state.d.ts +1 -1
  120. package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -1
  121. package/dest/test/index.d.ts +1 -1
  122. package/dest/test/rollup_cheat_codes.d.ts +23 -20
  123. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  124. package/dest/test/rollup_cheat_codes.js +79 -42
  125. package/dest/test/start_anvil.d.ts +2 -1
  126. package/dest/test/start_anvil.d.ts.map +1 -1
  127. package/dest/test/start_anvil.js +2 -1
  128. package/dest/test/tx_delayer.d.ts +1 -1
  129. package/dest/test/tx_delayer.d.ts.map +1 -1
  130. package/dest/test/tx_delayer.js +3 -2
  131. package/dest/test/upgrade_utils.d.ts +1 -1
  132. package/dest/test/upgrade_utils.d.ts.map +1 -1
  133. package/dest/test/upgrade_utils.js +3 -2
  134. package/dest/types.d.ts +57 -2
  135. package/dest/types.d.ts.map +1 -1
  136. package/dest/utils.d.ts +2 -2
  137. package/dest/utils.d.ts.map +1 -1
  138. package/dest/utils.js +10 -161
  139. package/dest/zkPassportVerifierAddress.d.ts +1 -1
  140. package/dest/zkPassportVerifierAddress.js +1 -1
  141. package/package.json +27 -13
  142. package/src/client.ts +1 -1
  143. package/src/config.ts +177 -65
  144. package/src/contracts/empire_base.ts +7 -6
  145. package/src/contracts/empire_slashing_proposer.ts +18 -8
  146. package/src/contracts/fee_asset_handler.ts +1 -1
  147. package/src/contracts/governance.ts +3 -3
  148. package/src/contracts/governance_proposer.ts +14 -9
  149. package/src/contracts/multicall.ts +12 -10
  150. package/src/contracts/rollup.ts +170 -171
  151. package/src/contracts/slasher_contract.ts +22 -0
  152. package/src/contracts/tally_slashing_proposer.ts +63 -12
  153. package/src/deploy_l1_contracts.ts +610 -337
  154. package/src/forwarder_proxy.ts +108 -0
  155. package/src/l1_artifacts.ts +14 -6
  156. package/src/l1_contract_addresses.ts +17 -26
  157. package/src/l1_reader.ts +17 -9
  158. package/src/l1_tx_utils/README.md +177 -0
  159. package/src/l1_tx_utils/config.ts +161 -0
  160. package/src/l1_tx_utils/constants.ts +18 -0
  161. package/src/l1_tx_utils/factory.ts +64 -0
  162. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +119 -0
  163. package/src/l1_tx_utils/index-blobs.ts +2 -0
  164. package/src/l1_tx_utils/index.ts +12 -0
  165. package/src/l1_tx_utils/interfaces.ts +86 -0
  166. package/src/l1_tx_utils/l1_tx_utils.ts +738 -0
  167. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  168. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +557 -0
  169. package/src/l1_tx_utils/signer.ts +28 -0
  170. package/src/l1_tx_utils/types.ts +85 -0
  171. package/src/l1_tx_utils/utils.ts +16 -0
  172. package/src/publisher_manager.ts +51 -9
  173. package/src/queries.ts +16 -8
  174. package/src/test/chain_monitor.ts +118 -36
  175. package/src/test/delayed_tx_utils.ts +2 -2
  176. package/src/test/eth_cheat_codes.ts +149 -30
  177. package/src/test/rollup_cheat_codes.ts +94 -52
  178. package/src/test/start_anvil.ts +2 -0
  179. package/src/test/tx_delayer.ts +4 -2
  180. package/src/test/upgrade_utils.ts +3 -2
  181. package/src/types.ts +62 -0
  182. package/src/utils.ts +12 -184
  183. package/src/zkPassportVerifierAddress.ts +1 -1
  184. package/dest/index.d.ts +0 -18
  185. package/dest/index.d.ts.map +0 -1
  186. package/dest/index.js +0 -17
  187. package/dest/l1_tx_utils.d.ts +0 -250
  188. package/dest/l1_tx_utils.d.ts.map +0 -1
  189. package/dest/l1_tx_utils.js +0 -826
  190. package/dest/l1_tx_utils_with_blobs.d.ts +0 -19
  191. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  192. package/dest/l1_tx_utils_with_blobs.js +0 -85
  193. package/src/index.ts +0 -17
  194. package/src/l1_tx_utils.ts +0 -1105
  195. package/src/l1_tx_utils_with_blobs.ts +0 -144
@@ -1,15 +1,17 @@
1
1
  import { L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/constants';
2
+ import { SlotNumber } from '@aztec/foundation/branded-types';
2
3
  import { SecretValue, getActiveNetworkName } from '@aztec/foundation/config';
3
- import { keccak256String } from '@aztec/foundation/crypto';
4
+ import { keccak256String } from '@aztec/foundation/crypto/keccak';
5
+ import type { Fr } from '@aztec/foundation/curves/bn254';
4
6
  import { EthAddress } from '@aztec/foundation/eth-address';
5
- import type { Fr } from '@aztec/foundation/fields';
6
7
  import { jsonStringify } from '@aztec/foundation/json-rpc';
7
8
  import { type Logger, createLogger } from '@aztec/foundation/log';
8
9
  import { DateProvider } from '@aztec/foundation/timer';
9
- import type { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
10
+ import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
10
11
 
11
12
  import type { Abi, Narrow } from 'abitype';
12
- import { mkdir, writeFile } from 'fs/promises';
13
+ import fs from 'fs';
14
+ import chunk from 'lodash.chunk';
13
15
  import {
14
16
  type Chain,
15
17
  type ContractConstructorArgs,
@@ -33,7 +35,6 @@ import { createExtendedL1Client } from './client.js';
33
35
  import {
34
36
  type L1ContractsConfig,
35
37
  getEntryQueueConfig,
36
- getGSEConfiguration,
37
38
  getGovernanceConfiguration,
38
39
  getRewardBoostConfig,
39
40
  getRewardConfig,
@@ -45,6 +46,7 @@ import { RegistryContract } from './contracts/registry.js';
45
46
  import { RollupContract, SlashingProposerType } from './contracts/rollup.js';
46
47
  import {
47
48
  CoinIssuerArtifact,
49
+ DateGatedRelayerArtifact,
48
50
  FeeAssetArtifact,
49
51
  FeeAssetHandlerArtifact,
50
52
  GSEArtifact,
@@ -63,21 +65,19 @@ import {
63
65
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
64
66
  import {
65
67
  type GasPrice,
66
- type L1GasConfig,
68
+ type L1TxConfig,
67
69
  type L1TxRequest,
68
70
  L1TxUtils,
69
71
  type L1TxUtilsConfig,
70
72
  createL1TxUtilsFromViemWallet,
71
73
  getL1TxUtilsConfigEnvVars,
72
- } from './l1_tx_utils.js';
74
+ } from './l1_tx_utils/index.js';
73
75
  import type { ExtendedViemWalletClient } from './types.js';
74
76
  import { formatViemError } from './utils.js';
75
77
  import { ZK_PASSPORT_DOMAIN, ZK_PASSPORT_SCOPE, ZK_PASSPORT_VERIFIER_ADDRESS } from './zkPassportVerifierAddress.js';
76
78
 
77
79
  export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
78
80
 
79
- const networkName = getActiveNetworkName();
80
-
81
81
  export type Operator = {
82
82
  attester: EthAddress;
83
83
  withdrawer: EthAddress;
@@ -148,8 +148,8 @@ export type VerificationRecord = {
148
148
  export interface DeployL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1TxUtilsConfig> {
149
149
  /** The vk tree root. */
150
150
  vkTreeRoot: Fr;
151
- /** The protocol contract tree root. */
152
- protocolContractTreeRoot: Fr;
151
+ /** The hash of the protocol contracts. */
152
+ protocolContractsHash: Fr;
153
153
  /** The genesis root of the archive tree. */
154
154
  genesisArchiveRoot: Fr;
155
155
  /** The salt for CREATE2 deployment. */
@@ -166,6 +166,8 @@ export interface DeployL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1T
166
166
  realVerifier: boolean;
167
167
  /** The zk passport args */
168
168
  zkPassportArgs?: ZKPassportArgs;
169
+ /** If provided, use this token for BOTH fee and staking assets (skip deployments) */
170
+ existingTokenAddress?: EthAddress;
169
171
  }
170
172
 
171
173
  export interface ZKPassportArgs {
@@ -177,39 +179,147 @@ export interface ZKPassportArgs {
177
179
  zkPassportScope?: string;
178
180
  }
179
181
 
182
+ // Minimal ERC20 ABI for validation purposes. We only read view methods.
183
+ const ERC20_VALIDATION_ABI = [
184
+ {
185
+ type: 'function',
186
+ name: 'totalSupply',
187
+ stateMutability: 'view',
188
+ inputs: [],
189
+ outputs: [{ name: '', type: 'uint256' }],
190
+ },
191
+ {
192
+ type: 'function',
193
+ name: 'name',
194
+ stateMutability: 'view',
195
+ inputs: [],
196
+ outputs: [{ name: '', type: 'string' }],
197
+ },
198
+ {
199
+ type: 'function',
200
+ name: 'symbol',
201
+ stateMutability: 'view',
202
+ inputs: [],
203
+ outputs: [{ name: '', type: 'string' }],
204
+ },
205
+ {
206
+ type: 'function',
207
+ name: 'decimals',
208
+ stateMutability: 'view',
209
+ inputs: [],
210
+ outputs: [{ name: '', type: 'uint8' }],
211
+ },
212
+ ] as const;
213
+
214
+ /**
215
+ * Validates that the provided address points to a contract that resembles an ERC20 token.
216
+ * Checks for contract code and attempts common ERC20 view calls.
217
+ * Throws an error if validation fails.
218
+ */
219
+ export async function validateExistingErc20TokenAddress(
220
+ l1Client: ExtendedViemWalletClient,
221
+ tokenAddress: EthAddress,
222
+ logger: Logger,
223
+ ): Promise<void> {
224
+ const addressString = tokenAddress.toString();
225
+
226
+ // Ensure there is contract code at the address
227
+ const code = await l1Client.getCode({ address: addressString });
228
+ if (!code || code === '0x') {
229
+ throw new Error(`No contract code found at provided token address ${addressString}`);
230
+ }
231
+
232
+ const contract = getContract({
233
+ address: getAddress(addressString),
234
+ abi: ERC20_VALIDATION_ABI,
235
+ client: l1Client,
236
+ });
237
+
238
+ // Validate all required ERC20 methods in parallel
239
+ const checks = [
240
+ contract.read.totalSupply().then(total => typeof total === 'bigint'),
241
+ contract.read.name().then(() => true),
242
+ contract.read.symbol().then(() => true),
243
+ contract.read.decimals().then(dec => typeof dec === 'number' || typeof dec === 'bigint'),
244
+ ];
245
+
246
+ const results = await Promise.allSettled(checks);
247
+ const failedChecks = results.filter(result => result.status === 'rejected' || result.value !== true);
248
+
249
+ if (failedChecks.length > 0) {
250
+ throw new Error(`Address ${addressString} does not appear to implement ERC20 view methods`);
251
+ }
252
+
253
+ logger.verbose(`Validated existing token at ${addressString} appears to be ERC20-compatible`);
254
+ }
255
+
180
256
  export const deploySharedContracts = async (
181
257
  l1Client: ExtendedViemWalletClient,
182
258
  deployer: L1Deployer,
183
259
  args: DeployL1ContractsArgs,
184
260
  logger: Logger,
185
261
  ) => {
186
- logger.info(`Deploying shared contracts for network configration: ${networkName}`);
262
+ const networkName = getActiveNetworkName();
263
+
264
+ logger.info(`Deploying shared contracts for network configuration: ${networkName}`);
187
265
 
188
266
  const txHashes: Hex[] = [];
189
267
 
190
- const feeAssetAddress = await deployer.deploy(FeeAssetArtifact, ['FeeJuice', 'FEE', l1Client.account.address]);
191
- logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
268
+ let feeAssetAddress: EthAddress;
269
+ let stakingAssetAddress: EthAddress;
270
+ if (args.existingTokenAddress) {
271
+ await validateExistingErc20TokenAddress(l1Client, args.existingTokenAddress, logger);
272
+ feeAssetAddress = args.existingTokenAddress;
273
+ stakingAssetAddress = args.existingTokenAddress;
274
+ logger.verbose(`Using existing token for fee and staking assets at ${args.existingTokenAddress}`);
275
+ } else {
276
+ const deployedFee = await deployer.deploy(FeeAssetArtifact, ['FeeJuice', 'FEE', l1Client.account.address]);
277
+ feeAssetAddress = deployedFee.address;
278
+ logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
192
279
 
193
- const stakingAssetAddress = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
194
- logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
280
+ // Mint a tiny bit of tokens to satisfy coin-issuer constraints
281
+ const { txHash } = await deployer.sendTransaction(
282
+ {
283
+ to: feeAssetAddress.toString(),
284
+ data: encodeFunctionData({
285
+ abi: FeeAssetArtifact.contractAbi,
286
+ functionName: 'mint',
287
+ args: [l1Client.account.address, 1n * 10n ** 18n],
288
+ }),
289
+ },
290
+ {
291
+ // contract may not have been deployed yet (CREATE2 returns address before mining),
292
+ // which causes gas estimation to fail. Hardcode to 100k which is plenty for ERC20 mint.
293
+ gasLimit: 100_000n,
294
+ },
295
+ );
296
+ await l1Client.waitForTransactionReceipt({ hash: txHash });
297
+ logger.verbose(`Minted tiny bit of tokens to satisfy coin-issuer constraints in ${txHash}`);
195
298
 
196
- const gseConfiguration = getGSEConfiguration(networkName);
299
+ const deployedStaking = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
300
+ stakingAssetAddress = deployedStaking.address;
301
+ logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
197
302
 
198
- const gseAddress = await deployer.deploy(GSEArtifact, [
199
- l1Client.account.address,
200
- stakingAssetAddress.toString(),
201
- gseConfiguration.activationThreshold,
202
- gseConfiguration.ejectionThreshold,
203
- ]);
303
+ await deployer.waitForDeployments();
304
+ }
305
+
306
+ const gseAddress = (
307
+ await deployer.deploy(GSEArtifact, [
308
+ l1Client.account.address,
309
+ stakingAssetAddress.toString(),
310
+ args.activationThreshold,
311
+ args.ejectionThreshold,
312
+ ])
313
+ ).address;
204
314
  logger.verbose(`Deployed GSE at ${gseAddress}`);
205
315
 
206
- const registryAddress = await deployer.deploy(RegistryArtifact, [
316
+ const { address: registryAddress } = await deployer.deploy(RegistryArtifact, [
207
317
  l1Client.account.address,
208
318
  feeAssetAddress.toString(),
209
319
  ]);
210
320
  logger.verbose(`Deployed Registry at ${registryAddress}`);
211
321
 
212
- const governanceProposerAddress = await deployer.deploy(GovernanceProposerArtifact, [
322
+ const { address: governanceProposerAddress } = await deployer.deploy(GovernanceProposerArtifact, [
213
323
  registryAddress.toString(),
214
324
  gseAddress.toString(),
215
325
  BigInt(args.governanceProposerQuorum ?? args.governanceProposerRoundSize / 2 + 1),
@@ -219,7 +329,7 @@ export const deploySharedContracts = async (
219
329
 
220
330
  // @note @LHerskind the assets are expected to be the same at some point, but for better
221
331
  // configurability they are different for now.
222
- const governanceAddress = await deployer.deploy(GovernanceArtifact, [
332
+ const { address: governanceAddress } = await deployer.deploy(GovernanceArtifact, [
223
333
  stakingAssetAddress.toString(),
224
334
  governanceProposerAddress.toString(),
225
335
  gseAddress.toString(),
@@ -261,11 +371,20 @@ export const deploySharedContracts = async (
261
371
  txHashes.push(txHash);
262
372
  }
263
373
 
264
- const coinIssuerAddress = await deployer.deploy(CoinIssuerArtifact, [
265
- feeAssetAddress.toString(),
266
- 1_000_000n * 10n ** 18n, // @todo #8084
267
- l1Client.account.address,
268
- ]);
374
+ logger.verbose(`Waiting for deployments to complete`);
375
+ await deployer.waitForDeployments();
376
+
377
+ const coinIssuerAddress = (
378
+ await deployer.deploy(
379
+ CoinIssuerArtifact,
380
+ [
381
+ feeAssetAddress.toString(),
382
+ 2n * 10n ** 17n, // hard cap of 20% per year
383
+ l1Client.account.address,
384
+ ],
385
+ { gasLimit: 1_000_000n, noSimulation: true },
386
+ )
387
+ ).address;
269
388
  logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
270
389
 
271
390
  logger.verbose(`Waiting for deployments to complete`);
@@ -277,21 +396,22 @@ export const deploySharedContracts = async (
277
396
  let stakingAssetHandlerAddress: EthAddress | undefined = undefined;
278
397
  let zkPassportVerifierAddress: EthAddress | undefined = undefined;
279
398
 
280
- // Only if not on mainnet will we deploy the handlers
281
- if (l1Client.chain.id !== 1) {
399
+ // Only if not on mainnet will we deploy the handlers, and only when we control the token
400
+ if (l1Client.chain.id !== 1 && !args.existingTokenAddress) {
282
401
  /* -------------------------------------------------------------------------- */
283
402
  /* CHEAT CODES START HERE */
284
403
  /* -------------------------------------------------------------------------- */
285
404
 
286
- feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
405
+ const deployedFeeAssetHandler = await deployer.deploy(FeeAssetHandlerArtifact, [
287
406
  l1Client.account.address,
288
407
  feeAssetAddress.toString(),
289
408
  BigInt(1000n * 10n ** 18n),
290
409
  ]);
410
+ feeAssetHandlerAddress = deployedFeeAssetHandler.address;
291
411
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
292
412
 
293
- // Only if we are "fresh" will we be adding as a minter, otherwise above will simply get same address
294
- if (needToSetGovernance) {
413
+ // Only add as minter if this is a new deployment (not reusing existing handler from failed previous run)
414
+ if (!deployedFeeAssetHandler.existed) {
295
415
  const { txHash } = await deployer.sendTransaction({
296
416
  to: feeAssetAddress.toString(),
297
417
  data: encodeFunctionData({
@@ -316,6 +436,7 @@ export const deploySharedContracts = async (
316
436
  stakingAsset: stakingAssetAddress.toString(),
317
437
  registry: registryAddress.toString(),
318
438
  withdrawer: AMIN.toString(),
439
+ validatorsToFlush: 16n,
319
440
  mintInterval: BigInt(60 * 60 * 24),
320
441
  depositsPerMint: BigInt(10),
321
442
  depositMerkleRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
@@ -329,7 +450,8 @@ export const deploySharedContracts = async (
329
450
  skipMerkleCheck: true, // skip merkle check - needed for testing without generating proofs
330
451
  } as const;
331
452
 
332
- stakingAssetHandlerAddress = await deployer.deploy(StakingAssetHandlerArtifact, [stakingAssetHandlerDeployArgs]);
453
+ stakingAssetHandlerAddress = (await deployer.deploy(StakingAssetHandlerArtifact, [stakingAssetHandlerDeployArgs]))
454
+ .address;
333
455
  logger.verbose(`Deployed StakingAssetHandler at ${stakingAssetHandlerAddress}`);
334
456
 
335
457
  const { txHash: stakingMinterTxHash } = await deployer.sendTransaction({
@@ -365,19 +487,23 @@ export const deploySharedContracts = async (
365
487
 
366
488
  const rewardDistributorAddress = await registry.getRewardDistributor();
367
489
 
368
- const blockReward = getRewardConfig(networkName).blockReward;
490
+ if (!args.existingTokenAddress) {
491
+ const checkpointReward = getRewardConfig(networkName).checkpointReward;
369
492
 
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
- });
493
+ const funding = checkpointReward * 200000n;
494
+ const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
495
+ to: feeAssetAddress.toString(),
496
+ data: encodeFunctionData({
497
+ abi: FeeAssetArtifact.contractAbi,
498
+ functionName: 'mint',
499
+ args: [rewardDistributorAddress.toString(), funding],
500
+ }),
501
+ });
379
502
 
380
- logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
503
+ logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
504
+ } else {
505
+ logger.verbose(`Skipping reward distributor funding as existing token is provided`);
506
+ }
381
507
 
382
508
  /* -------------------------------------------------------------------------- */
383
509
  /* FUND REWARD DISTRIBUTOR STOP */
@@ -400,7 +526,7 @@ export const deploySharedContracts = async (
400
526
 
401
527
  const getZkPassportVerifierAddress = async (deployer: L1Deployer, args: DeployL1ContractsArgs): Promise<EthAddress> => {
402
528
  if (args.zkPassportArgs?.mockZkPassportVerifier) {
403
- return await deployer.deploy(mockVerifiers.mockZkPassportVerifier);
529
+ return (await deployer.deploy(mockVerifiers.mockZkPassportVerifier)).address;
404
530
  }
405
531
  return ZK_PASSPORT_VERIFIER_ADDRESS;
406
532
  };
@@ -416,6 +542,199 @@ const getZkPassportScopes = (args: DeployL1ContractsArgs): [string, string] => {
416
542
  return [domain, scope];
417
543
  };
418
544
 
545
+ /**
546
+ * Generates verification records for a deployed rollup and its associated contracts (Inbox, Outbox, Slasher, etc).
547
+ * @param rollup - The deployed rollup contract.
548
+ * @param deployer - The L1 deployer instance.
549
+ * @param args - The deployment arguments used for the rollup.
550
+ * @param addresses - The L1 contract addresses.
551
+ * @param extendedClient - The extended viem wallet client.
552
+ * @param logger - The logger.
553
+ */
554
+ async function generateRollupVerificationRecords(
555
+ rollup: RollupContract,
556
+ deployer: L1Deployer,
557
+ args: {
558
+ slashingVetoer: EthAddress;
559
+ slashingRoundSizeInEpochs: number;
560
+ aztecEpochDuration: number;
561
+ slashingQuorum?: number;
562
+ slashingLifetimeInRounds: number;
563
+ slashingExecutionDelayInRounds: number;
564
+ slasherFlavor: 'none' | 'tally' | 'empire';
565
+ slashAmountSmall: bigint;
566
+ slashAmountMedium: bigint;
567
+ slashAmountLarge: bigint;
568
+ aztecTargetCommitteeSize: number;
569
+ slashingOffsetInRounds: number;
570
+ },
571
+ addresses: Pick<L1ContractAddresses, 'feeJuiceAddress'>,
572
+ extendedClient: ExtendedViemWalletClient,
573
+ logger: Logger,
574
+ ): Promise<void> {
575
+ try {
576
+ // Add Inbox / Outbox verification records (constructor args are created inside RollupCore)
577
+ const rollupAddr = rollup.address;
578
+ const rollupAddresses = await rollup.getRollupAddresses();
579
+ const inboxAddr = rollupAddresses.inboxAddress.toString();
580
+ const outboxAddr = rollupAddresses.outboxAddress.toString();
581
+ const feeAsset = rollupAddresses.feeJuiceAddress.toString();
582
+ const version = await rollup.getVersion();
583
+
584
+ const inboxCtor = encodeAbiParameters(
585
+ [{ type: 'address' }, { type: 'address' }, { type: 'uint256' }, { type: 'uint256' }],
586
+ [rollupAddr, feeAsset, version, BigInt(L1_TO_L2_MSG_SUBTREE_HEIGHT)],
587
+ );
588
+
589
+ const outboxCtor = encodeAbiParameters([{ type: 'address' }, { type: 'uint256' }], [rollupAddr, version]);
590
+
591
+ deployer.verificationRecords.push(
592
+ { name: 'Inbox', address: inboxAddr, constructorArgsHex: inboxCtor, libraries: [] },
593
+ { name: 'Outbox', address: outboxAddr, constructorArgsHex: outboxCtor, libraries: [] },
594
+ );
595
+
596
+ // Include Slasher and SlashingProposer (if deployed) in verification data
597
+ try {
598
+ const slasherAddrHex = await rollup.getSlasherAddress();
599
+ const slasherAddr = EthAddress.fromString(slasherAddrHex);
600
+ if (!slasherAddr.isZero()) {
601
+ // Slasher constructor: (address _vetoer, address _governance)
602
+ const slasherCtor = encodeAbiParameters(
603
+ [{ type: 'address' }, { type: 'address' }],
604
+ [args.slashingVetoer.toString(), extendedClient.account.address],
605
+ );
606
+ deployer.verificationRecords.push({
607
+ name: 'Slasher',
608
+ address: slasherAddr.toString(),
609
+ constructorArgsHex: slasherCtor,
610
+ libraries: [],
611
+ });
612
+
613
+ // Proposer address is stored in Slasher.PROPOSER()
614
+ const proposerAddr = (await rollup.getSlashingProposerAddress()).toString();
615
+
616
+ // Compute constructor args matching deployment path in RollupCore
617
+ const computedRoundSize = BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration);
618
+ const computedQuorum = BigInt(
619
+ args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1,
620
+ );
621
+ const lifetimeInRounds = BigInt(args.slashingLifetimeInRounds);
622
+ const executionDelayInRounds = BigInt(args.slashingExecutionDelayInRounds);
623
+
624
+ if (args.slasherFlavor === 'tally') {
625
+ const slashAmounts: readonly [bigint, bigint, bigint] = [
626
+ args.slashAmountSmall,
627
+ args.slashAmountMedium,
628
+ args.slashAmountLarge,
629
+ ];
630
+ const committeeSize = BigInt(args.aztecTargetCommitteeSize);
631
+ const epochDuration = BigInt(args.aztecEpochDuration);
632
+ const slashOffsetInRounds = BigInt(args.slashingOffsetInRounds);
633
+
634
+ const proposerCtor = encodeAbiParameters(
635
+ [
636
+ { type: 'address' },
637
+ { type: 'address' },
638
+ { type: 'uint256' },
639
+ { type: 'uint256' },
640
+ { type: 'uint256' },
641
+ { type: 'uint256' },
642
+ { type: 'uint256[3]' },
643
+ { type: 'uint256' },
644
+ { type: 'uint256' },
645
+ { type: 'uint256' },
646
+ ],
647
+ [
648
+ rollup.address,
649
+ slasherAddr.toString(),
650
+ computedQuorum,
651
+ computedRoundSize,
652
+ lifetimeInRounds,
653
+ executionDelayInRounds,
654
+ slashAmounts,
655
+ committeeSize,
656
+ epochDuration,
657
+ slashOffsetInRounds,
658
+ ],
659
+ );
660
+
661
+ deployer.verificationRecords.push({
662
+ name: 'TallySlashingProposer',
663
+ address: proposerAddr,
664
+ constructorArgsHex: proposerCtor,
665
+ libraries: [],
666
+ });
667
+ } else if (args.slasherFlavor === 'empire') {
668
+ const proposerCtor = encodeAbiParameters(
669
+ [
670
+ { type: 'address' },
671
+ { type: 'address' },
672
+ { type: 'uint256' },
673
+ { type: 'uint256' },
674
+ { type: 'uint256' },
675
+ { type: 'uint256' },
676
+ ],
677
+ [
678
+ rollup.address,
679
+ slasherAddr.toString(),
680
+ computedQuorum,
681
+ computedRoundSize,
682
+ lifetimeInRounds,
683
+ executionDelayInRounds,
684
+ ],
685
+ );
686
+
687
+ deployer.verificationRecords.push({
688
+ name: 'EmpireSlashingProposer',
689
+ address: proposerAddr,
690
+ constructorArgsHex: proposerCtor,
691
+ libraries: [],
692
+ });
693
+ }
694
+ }
695
+ } catch (e) {
696
+ logger.warn(`Failed to add Slasher/Proposer verification records: ${String(e)}`);
697
+ }
698
+ } catch (e) {
699
+ throw new Error(`Failed to generate rollup verification records: ${String(e)}`);
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Writes verification records to a JSON file for later forge verify.
705
+ * @param deployer - The L1 deployer containing verification records.
706
+ * @param outputDirectory - The directory to write the verification file to.
707
+ * @param chainId - The chain ID.
708
+ * @param filenameSuffix - Optional suffix for the filename (e.g., 'upgrade').
709
+ * @param logger - The logger.
710
+ */
711
+ async function writeVerificationJson(
712
+ deployer: L1Deployer,
713
+ outputDirectory: string,
714
+ chainId: number,
715
+ filenameSuffix: string = '',
716
+ logger: Logger,
717
+ ): Promise<void> {
718
+ try {
719
+ const date = new Date();
720
+ const formattedDate = date.toISOString().slice(2, 19).replace(/[-T:]/g, '');
721
+ // Ensure the verification output directory exists
722
+ await fs.promises.mkdir(outputDirectory, { recursive: true });
723
+ const suffix = filenameSuffix ? `-${filenameSuffix}` : '';
724
+ const verificationOutputPath = `${outputDirectory}/l1-verify${suffix}-${chainId}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
725
+ const networkName = getActiveNetworkName();
726
+ const verificationData = {
727
+ chainId: chainId,
728
+ network: networkName,
729
+ records: deployer.verificationRecords,
730
+ };
731
+ await fs.promises.writeFile(verificationOutputPath, JSON.stringify(verificationData, null, 2));
732
+ logger.info(`Wrote L1 verification data to ${verificationOutputPath}`);
733
+ } catch (e) {
734
+ logger.warn(`Failed to write L1 verification data file: ${String(e)}`);
735
+ }
736
+ }
737
+
419
738
  /**
420
739
  * Deploys a new rollup, using the existing canonical version to derive certain values (addresses of assets etc).
421
740
  * @param clients - The L1 clients.
@@ -423,6 +742,7 @@ const getZkPassportScopes = (args: DeployL1ContractsArgs): [string, string] => {
423
742
  * @param registryAddress - The address of the registry.
424
743
  * @param logger - The logger.
425
744
  * @param txUtilsConfig - The L1 tx utils config.
745
+ * @param createVerificationJson - Optional path to write verification data for forge verify.
426
746
  */
427
747
  export const deployRollupForUpgrade = async (
428
748
  extendedClient: ExtendedViemWalletClient,
@@ -433,6 +753,7 @@ export const deployRollupForUpgrade = async (
433
753
  registryAddress: EthAddress,
434
754
  logger: Logger,
435
755
  txUtilsConfig: L1TxUtilsConfig,
756
+ createVerificationJson: string | false = false,
436
757
  ) => {
437
758
  const deployer = new L1Deployer(
438
759
  extendedClient,
@@ -441,6 +762,7 @@ export const deployRollupForUpgrade = async (
441
762
  args.acceleratedTestDeployments,
442
763
  logger,
443
764
  txUtilsConfig,
765
+ !!createVerificationJson,
444
766
  );
445
767
 
446
768
  const addresses = await RegistryContract.collectAddresses(extendedClient, registryAddress, 'canonical');
@@ -449,11 +771,17 @@ export const deployRollupForUpgrade = async (
449
771
 
450
772
  await deployer.waitForDeployments();
451
773
 
774
+ // Write verification data (constructor args + linked libraries) to file for later forge verify
775
+ if (createVerificationJson) {
776
+ await generateRollupVerificationRecords(rollup, deployer, args, addresses, extendedClient, logger);
777
+ await writeVerificationJson(deployer, createVerificationJson, extendedClient.chain.id, 'upgrade', logger);
778
+ }
779
+
452
780
  return { rollup, slashFactoryAddress };
453
781
  };
454
782
 
455
783
  export const deploySlashFactory = async (deployer: L1Deployer, rollupAddress: Hex, logger: Logger) => {
456
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [rollupAddress]);
784
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress])).address;
457
785
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
458
786
  return slashFactoryAddress;
459
787
  };
@@ -462,10 +790,12 @@ export const deployUpgradePayload = async (
462
790
  deployer: L1Deployer,
463
791
  addresses: Pick<L1ContractAddresses, 'registryAddress' | 'rollupAddress'>,
464
792
  ) => {
465
- const payloadAddress = await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
466
- addresses.registryAddress.toString(),
467
- addresses.rollupAddress.toString(),
468
- ]);
793
+ const payloadAddress = (
794
+ await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
795
+ addresses.registryAddress.toString(),
796
+ addresses.rollupAddress.toString(),
797
+ ])
798
+ ).address;
469
799
 
470
800
  return payloadAddress;
471
801
  };
@@ -509,6 +839,7 @@ export const deployRollup = async (
509
839
  if (!addresses.gseAddress) {
510
840
  throw new Error('GSE address is required when deploying');
511
841
  }
842
+ const networkName = getActiveNetworkName();
512
843
 
513
844
  logger.info(`Deploying rollup using network configuration: ${networkName}`);
514
845
 
@@ -517,10 +848,10 @@ export const deployRollup = async (
517
848
  let epochProofVerifier = EthAddress.ZERO;
518
849
 
519
850
  if (args.realVerifier) {
520
- epochProofVerifier = await deployer.deploy(l1ArtifactsVerifiers.honkVerifier);
851
+ epochProofVerifier = (await deployer.deploy(l1ArtifactsVerifiers.honkVerifier)).address;
521
852
  logger.verbose(`Rollup will use the real verifier at ${epochProofVerifier}`);
522
853
  } else {
523
- epochProofVerifier = await deployer.deploy(mockVerifiers.mockVerifier);
854
+ epochProofVerifier = (await deployer.deploy(mockVerifiers.mockVerifier)).address;
524
855
  logger.verbose(`Rollup will use the mock verifier at ${epochProofVerifier}`);
525
856
  }
526
857
 
@@ -533,6 +864,8 @@ export const deployRollup = async (
533
864
  aztecSlotDuration: BigInt(args.aztecSlotDuration),
534
865
  aztecEpochDuration: BigInt(args.aztecEpochDuration),
535
866
  targetCommitteeSize: BigInt(args.aztecTargetCommitteeSize),
867
+ lagInEpochsForValidatorSet: BigInt(args.lagInEpochsForValidatorSet),
868
+ lagInEpochsForRandao: BigInt(args.lagInEpochsForRandao),
536
869
  aztecProofSubmissionEpochs: BigInt(args.aztecProofSubmissionEpochs),
537
870
  slashingQuorum: BigInt(args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1),
538
871
  slashingRoundSize: BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration),
@@ -543,17 +876,20 @@ export const deployRollup = async (
543
876
  provingCostPerMana: args.provingCostPerMana,
544
877
  rewardConfig: rewardConfig,
545
878
  version: 0,
546
- rewardBoostConfig: getRewardBoostConfig(networkName),
879
+ rewardBoostConfig: getRewardBoostConfig(),
547
880
  stakingQueueConfig: getEntryQueueConfig(networkName),
548
881
  exitDelaySeconds: BigInt(args.exitDelaySeconds),
549
882
  slasherFlavor: slasherFlavorToSolidityEnum(args.slasherFlavor),
550
883
  slashingOffsetInRounds: BigInt(args.slashingOffsetInRounds),
551
884
  slashAmounts: [args.slashAmountSmall, args.slashAmountMedium, args.slashAmountLarge],
885
+ localEjectionThreshold: args.localEjectionThreshold,
886
+ slashingDisableDuration: BigInt(args.slashingDisableDuration ?? 0n),
887
+ earliestRewardsClaimableTimestamp: 0n,
552
888
  };
553
889
 
554
890
  const genesisStateArgs = {
555
891
  vkTreeRoot: args.vkTreeRoot.toString(),
556
- protocolContractTreeRoot: args.protocolContractTreeRoot.toString(),
892
+ protocolContractsHash: args.protocolContractsHash.toString(),
557
893
  genesisArchiveRoot: args.genesisArchiveRoot.toString(),
558
894
  };
559
895
 
@@ -579,8 +915,10 @@ export const deployRollup = async (
579
915
  rollupConfigArgs,
580
916
  ] as const;
581
917
 
582
- const rollupAddress = await deployer.deploy(RollupArtifact, rollupArgs, { gasLimit: 15_000_000n });
583
- logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
918
+ const { address: rollupAddress, existed: rollupExisted } = await deployer.deploy(RollupArtifact, rollupArgs, {
919
+ gasLimit: 15_000_000n,
920
+ });
921
+ logger.verbose(`Deployed Rollup at ${rollupAddress}, already existed: ${rollupExisted}`, rollupConfigArgs);
584
922
 
585
923
  const rollupContract = new RollupContract(extendedClient, rollupAddress);
586
924
 
@@ -588,24 +926,29 @@ export const deployRollup = async (
588
926
  logger.verbose(`All core contracts have been deployed`);
589
927
 
590
928
  if (args.feeJuicePortalInitialBalance && args.feeJuicePortalInitialBalance > 0n) {
591
- const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
929
+ // Skip funding when using an external token, as we likely don't have mint permissions
930
+ if (!('existingTokenAddress' in args) || !args.existingTokenAddress) {
931
+ const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
592
932
 
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);
933
+ // In fast mode, use the L1TxUtils to send transactions with nonce management
934
+ const { txHash: mintTxHash } = await deployer.sendTransaction({
935
+ to: addresses.feeJuiceAddress.toString(),
936
+ data: encodeFunctionData({
937
+ abi: FeeAssetArtifact.contractAbi,
938
+ functionName: 'mint',
939
+ args: [feeJuicePortalAddress.toString(), args.feeJuicePortalInitialBalance],
940
+ }),
941
+ });
942
+ logger.verbose(
943
+ `Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`,
944
+ );
945
+ txHashes.push(mintTxHash);
946
+ } else {
947
+ logger.verbose('Skipping fee juice portal funding due to external token usage');
948
+ }
606
949
  }
607
950
 
608
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [rollupAddress.toString()]);
951
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress.toString()])).address;
609
952
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
610
953
 
611
954
  // We need to call a function on the registry to set the various contract addresses.
@@ -667,7 +1010,17 @@ export const deployRollup = async (
667
1010
  logger.verbose(`Not the owner of the gse, skipping rollup addition`);
668
1011
  }
669
1012
 
670
- if (args.initialValidators && (await gseContract.read.isRollupRegistered([rollupContract.address]))) {
1013
+ const activeAttestorCount = await rollupContract.getActiveAttesterCount();
1014
+ const queuedAttestorCount = await rollupContract.getEntryQueueLength();
1015
+ logger.info(`Rollup has ${activeAttestorCount} active attestors and ${queuedAttestorCount} queued attestors`);
1016
+
1017
+ const shouldAddValidators = activeAttestorCount === 0n && queuedAttestorCount === 0n;
1018
+
1019
+ if (
1020
+ args.initialValidators &&
1021
+ shouldAddValidators &&
1022
+ (await gseContract.read.isRollupRegistered([rollupContract.address]))
1023
+ ) {
671
1024
  await addMultipleValidators(
672
1025
  extendedClient,
673
1026
  deployer,
@@ -715,6 +1068,7 @@ export const handoverToGovernance = async (
715
1068
  governanceAddress: EthAddress,
716
1069
  logger: Logger,
717
1070
  acceleratedTestDeployments: boolean | undefined,
1071
+ useExternalToken: boolean = false,
718
1072
  ) => {
719
1073
  // We need to call a function on the registry to set the various contract addresses.
720
1074
  const registryContract = getContract({
@@ -780,7 +1134,10 @@ export const handoverToGovernance = async (
780
1134
  txHashes.push(transferOwnershipTxHash);
781
1135
  }
782
1136
 
783
- if (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString()) {
1137
+ if (
1138
+ !useExternalToken &&
1139
+ (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString())
1140
+ ) {
784
1141
  const { txHash } = await deployer.sendTransaction(
785
1142
  {
786
1143
  to: feeAssetAddress.toString(),
@@ -807,23 +1164,28 @@ export const handoverToGovernance = async (
807
1164
  );
808
1165
  logger.verbose(`Accept ownership of fee asset in ${acceptTokenOwnershipTxHash}`);
809
1166
  txHashes.push(acceptTokenOwnershipTxHash);
1167
+ } else if (useExternalToken) {
1168
+ logger.verbose('Skipping fee asset ownership transfer due to external token usage');
810
1169
  }
811
1170
 
1171
+ // Either deploy or at least predict the address of the date gated relayer
1172
+ const dateGatedRelayer = await deployer.deploy(DateGatedRelayerArtifact, [
1173
+ governanceAddress.toString(),
1174
+ 1798761600n, // 2027-01-01 00:00:00 UTC
1175
+ ]);
1176
+
812
1177
  // 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
- ) {
1178
+ if (acceleratedTestDeployments || (await coinIssuerContract.read.owner()) === deployer.client.account.address) {
817
1179
  const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
818
1180
  to: coinIssuerContract.address,
819
1181
  data: encodeFunctionData({
820
1182
  abi: CoinIssuerArtifact.contractAbi,
821
1183
  functionName: 'transferOwnership',
822
- args: [getAddress(governanceAddress.toString())],
1184
+ args: [getAddress(dateGatedRelayer.address.toString())],
823
1185
  }),
824
1186
  });
825
1187
  logger.verbose(
826
- `Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
1188
+ `Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the DateGatedRelayer ${dateGatedRelayer.address} in tx ${transferOwnershipTxHash}`,
827
1189
  );
828
1190
  txHashes.push(transferOwnershipTxHash);
829
1191
  }
@@ -831,6 +1193,8 @@ export const handoverToGovernance = async (
831
1193
  // Wait for all actions to be mined
832
1194
  await deployer.waitForDeployments();
833
1195
  await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
1196
+
1197
+ return { dateGatedRelayerAddress: dateGatedRelayer.address };
834
1198
  };
835
1199
 
836
1200
  /*
@@ -877,100 +1241,112 @@ export const addMultipleValidators = async (
877
1241
  validators = enrichedValidators.filter(v => v.status === 0).map(v => v.operator);
878
1242
  }
879
1243
 
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
- };
1244
+ if (validators.length === 0) {
1245
+ logger.warn('No validators to add. Skipping.');
1246
+ return;
1247
+ }
1248
+
1249
+ const gseContract = new GSEContract(extendedClient, gseAddress);
1250
+ const multiAdder = (await deployer.deploy(MultiAdderArtifact, [rollupAddress, deployer.client.account.address]))
1251
+ .address;
1252
+
1253
+ const makeValidatorTuples = async (validator: Operator) => {
1254
+ const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
1255
+ return {
1256
+ attester: getAddress(validator.attester.toString()),
1257
+ withdrawer: getAddress(validator.withdrawer.toString()),
1258
+ ...registrationTuple,
891
1259
  };
1260
+ };
892
1261
 
893
- const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
1262
+ const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
894
1263
 
895
- // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
896
- const stakeNeeded = activationThreshold * BigInt(validators.length);
1264
+ // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
1265
+ const stakeNeeded = activationThreshold * BigInt(validators.length);
897
1266
 
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
- });
1267
+ await deployer.l1TxUtils.sendAndMonitorTransaction({
1268
+ to: stakingAssetAddress,
1269
+ data: encodeFunctionData({
1270
+ abi: StakingAssetArtifact.contractAbi,
1271
+ functionName: 'mint',
1272
+ args: [multiAdder.toString(), stakeNeeded],
1273
+ }),
1274
+ });
906
1275
 
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
- );
1276
+ const entryQueueLengthBefore = await rollup.getEntryQueueLength();
1277
+ const validatorCountBefore = await rollup.getActiveAttesterCount();
928
1278
 
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
- }
1279
+ logger.info(`Adding ${validators.length} validators to the rollup`);
957
1280
 
958
- const entryQueueLengthAfter = await rollup.getEntryQueueLength();
959
- const validatorCountAfter = await rollup.getActiveAttesterCount();
1281
+ const chunkSize = 16;
960
1282
 
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
- );
1283
+ // We will add `chunkSize` validators to the queue until we have covered all of our validators.
1284
+ // The `chunkSize` needs to be small enough to fit inside a single tx, therefore 16.
1285
+ for (const c of chunk(validatorsTuples, chunkSize)) {
1286
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1287
+ {
1288
+ to: multiAdder.toString(),
1289
+ data: encodeFunctionData({
1290
+ abi: MultiAdderArtifact.contractAbi,
1291
+ functionName: 'addValidators',
1292
+ args: [c, BigInt(0)],
1293
+ }),
1294
+ },
1295
+ {
1296
+ gasLimit: 16_000_000n,
1297
+ },
1298
+ );
1299
+ }
1300
+
1301
+ // After adding to the queue, we will now try to flush from it.
1302
+ // We are explicitly doing this as a second step instead of as part of adding to benefit
1303
+ // from the accounting used to speed the process up.
1304
+ // As the queue computes the amount of possible flushes in an epoch when told to flush,
1305
+ // waiting until we have added all we want allows us to benefit in the case were we added
1306
+ // enough to pass the bootstrap set size without needing to wait another epoch.
1307
+ // This is useful when we are testing as it speeds up the tests slightly.
1308
+ while (true) {
1309
+ // If the queue is empty, we can break
1310
+ if ((await rollup.getEntryQueueLength()) == 0n) {
1311
+ break;
1312
+ }
1313
+
1314
+ // If there are no available validator flushes, no need to even try
1315
+ if ((await rollup.getAvailableValidatorFlushes()) == 0n) {
1316
+ break;
968
1317
  }
969
1318
 
970
- logger.info(
971
- `Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
1319
+ // Note that we are flushing at most `chunkSize` at each call
1320
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1321
+ {
1322
+ to: rollup.address,
1323
+ data: encodeFunctionData({
1324
+ abi: RollupArtifact.contractAbi,
1325
+ functionName: 'flushEntryQueue',
1326
+ args: [BigInt(chunkSize)],
1327
+ }),
1328
+ },
1329
+ {
1330
+ gasLimit: 16_000_000n,
1331
+ },
972
1332
  );
973
1333
  }
1334
+
1335
+ const entryQueueLengthAfter = await rollup.getEntryQueueLength();
1336
+ const validatorCountAfter = await rollup.getActiveAttesterCount();
1337
+
1338
+ if (
1339
+ entryQueueLengthAfter + validatorCountAfter <
1340
+ entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)
1341
+ ) {
1342
+ throw new Error(
1343
+ `Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}. A likely issue is the bootstrap size.`,
1344
+ );
1345
+ }
1346
+
1347
+ logger.info(
1348
+ `Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
1349
+ );
974
1350
  }
975
1351
  };
976
1352
 
@@ -993,11 +1369,13 @@ export const cheat_initializeFeeAssetHandler = async (
993
1369
  feeAssetHandlerAddress: EthAddress;
994
1370
  txHash: Hex;
995
1371
  }> => {
996
- const feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
997
- extendedClient.account.address,
998
- feeAssetAddress.toString(),
999
- BigInt(1e18),
1000
- ]);
1372
+ const feeAssetHandlerAddress = (
1373
+ await deployer.deploy(FeeAssetHandlerArtifact, [
1374
+ extendedClient.account.address,
1375
+ feeAssetAddress.toString(),
1376
+ BigInt(1e18),
1377
+ ])
1378
+ ).address;
1001
1379
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
1002
1380
 
1003
1381
  const { txHash } = await deployer.sendTransaction({
@@ -1033,6 +1411,13 @@ export const deployL1Contracts = async (
1033
1411
  logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
1034
1412
  validateConfig(args);
1035
1413
 
1414
+ if (args.initialValidators && args.initialValidators.length > 0 && args.existingTokenAddress) {
1415
+ throw new Error(
1416
+ 'Cannot deploy with both initialValidators and existingTokenAddress. ' +
1417
+ 'Initial validator funding requires minting tokens, which is not possible with an external token.',
1418
+ );
1419
+ }
1420
+
1036
1421
  const l1Client = createExtendedL1Client(rpcUrls, account, chain);
1037
1422
 
1038
1423
  // Deploy multicall3 if it does not exist in this network
@@ -1102,7 +1487,7 @@ export const deployL1Contracts = async (
1102
1487
  await deployer.waitForDeployments();
1103
1488
 
1104
1489
  // Now that the rollup has been deployed and added to the registry, transfer ownership to governance
1105
- await handoverToGovernance(
1490
+ const { dateGatedRelayerAddress } = await handoverToGovernance(
1106
1491
  l1Client,
1107
1492
  deployer,
1108
1493
  registryAddress,
@@ -1112,6 +1497,7 @@ export const deployL1Contracts = async (
1112
1497
  governanceAddress,
1113
1498
  logger,
1114
1499
  args.acceleratedTestDeployments,
1500
+ !!args.existingTokenAddress,
1115
1501
  );
1116
1502
 
1117
1503
  logger.info(`Handing over to governance complete`);
@@ -1123,143 +1509,8 @@ export const deployL1Contracts = async (
1123
1509
 
1124
1510
  // Write verification data (constructor args + linked libraries) to file for later forge verify
1125
1511
  if (createVerificationJson) {
1126
- try {
1127
- // Add Inbox / Outbox verification records (constructor args are created inside RollupCore)
1128
- const rollupAddr = l1Contracts.rollupAddress.toString();
1129
- const inboxAddr = l1Contracts.inboxAddress.toString();
1130
- const outboxAddr = l1Contracts.outboxAddress.toString();
1131
- const feeAsset = l1Contracts.feeJuiceAddress.toString();
1132
- const version = await rollup.getVersion();
1133
-
1134
- const inboxCtor = encodeAbiParameters(
1135
- [{ type: 'address' }, { type: 'address' }, { type: 'uint256' }, { type: 'uint256' }],
1136
- [rollupAddr, feeAsset, version, BigInt(L1_TO_L2_MSG_SUBTREE_HEIGHT)],
1137
- );
1138
-
1139
- const outboxCtor = encodeAbiParameters([{ type: 'address' }, { type: 'uint256' }], [rollupAddr, version]);
1140
-
1141
- deployer.verificationRecords.push(
1142
- { name: 'Inbox', address: inboxAddr, constructorArgsHex: inboxCtor, libraries: [] },
1143
- { name: 'Outbox', address: outboxAddr, constructorArgsHex: outboxCtor, libraries: [] },
1144
- );
1145
-
1146
- // Include Slasher and SlashingProposer (if deployed) in verification data
1147
- try {
1148
- const slasherAddrHex = await rollup.getSlasher();
1149
- const slasherAddr = EthAddress.fromString(slasherAddrHex);
1150
- if (!slasherAddr.isZero()) {
1151
- // Slasher constructor: (address _vetoer, address _governance)
1152
- const slasherCtor = encodeAbiParameters(
1153
- [{ type: 'address' }, { type: 'address' }],
1154
- [args.slashingVetoer.toString(), l1Client.account.address],
1155
- );
1156
- deployer.verificationRecords.push({
1157
- name: 'Slasher',
1158
- address: slasherAddr.toString(),
1159
- constructorArgsHex: slasherCtor,
1160
- libraries: [],
1161
- });
1162
-
1163
- // Proposer address is stored in Slasher.PROPOSER()
1164
- const proposerAddr = (await rollup.getSlashingProposerAddress()).toString();
1165
-
1166
- // Compute constructor args matching deployment path in RollupCore
1167
- const computedRoundSize = BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration);
1168
- const computedQuorum = BigInt(
1169
- args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1,
1170
- );
1171
- const lifetimeInRounds = BigInt(args.slashingLifetimeInRounds);
1172
- const executionDelayInRounds = BigInt(args.slashingExecutionDelayInRounds);
1173
-
1174
- if (args.slasherFlavor === 'tally') {
1175
- const slashAmounts: readonly [bigint, bigint, bigint] = [
1176
- args.slashAmountSmall,
1177
- args.slashAmountMedium,
1178
- args.slashAmountLarge,
1179
- ];
1180
- const committeeSize = BigInt(args.aztecTargetCommitteeSize);
1181
- const epochDuration = BigInt(args.aztecEpochDuration);
1182
- const slashOffsetInRounds = BigInt(args.slashingOffsetInRounds);
1183
-
1184
- const proposerCtor = encodeAbiParameters(
1185
- [
1186
- { type: 'address' },
1187
- { type: 'address' },
1188
- { type: 'uint256' },
1189
- { type: 'uint256' },
1190
- { type: 'uint256' },
1191
- { type: 'uint256' },
1192
- { type: 'uint256[3]' },
1193
- { type: 'uint256' },
1194
- { type: 'uint256' },
1195
- { type: 'uint256' },
1196
- ],
1197
- [
1198
- rollup.address,
1199
- slasherAddr.toString(),
1200
- computedQuorum,
1201
- computedRoundSize,
1202
- lifetimeInRounds,
1203
- executionDelayInRounds,
1204
- slashAmounts,
1205
- committeeSize,
1206
- epochDuration,
1207
- slashOffsetInRounds,
1208
- ],
1209
- );
1210
-
1211
- deployer.verificationRecords.push({
1212
- name: 'TallySlashingProposer',
1213
- address: proposerAddr,
1214
- constructorArgsHex: proposerCtor,
1215
- libraries: [],
1216
- });
1217
- } else if (args.slasherFlavor === 'empire') {
1218
- const proposerCtor = encodeAbiParameters(
1219
- [
1220
- { type: 'address' },
1221
- { type: 'address' },
1222
- { type: 'uint256' },
1223
- { type: 'uint256' },
1224
- { type: 'uint256' },
1225
- { type: 'uint256' },
1226
- ],
1227
- [
1228
- rollup.address,
1229
- slasherAddr.toString(),
1230
- computedQuorum,
1231
- computedRoundSize,
1232
- lifetimeInRounds,
1233
- executionDelayInRounds,
1234
- ],
1235
- );
1236
-
1237
- deployer.verificationRecords.push({
1238
- name: 'EmpireSlashingProposer',
1239
- address: proposerAddr,
1240
- constructorArgsHex: proposerCtor,
1241
- libraries: [],
1242
- });
1243
- }
1244
- }
1245
- } catch (e) {
1246
- logger.warn(`Failed to add Slasher/Proposer verification records: ${String(e)}`);
1247
- }
1248
- const date = new Date();
1249
- const formattedDate = date.toISOString().slice(2, 19).replace(/[-T:]/g, '');
1250
- // Ensure the verification output directory exists
1251
- await mkdir(createVerificationJson, { recursive: true });
1252
- const verificationOutputPath = `${createVerificationJson}/l1-verify-${chain.id}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
1253
- const verificationData = {
1254
- chainId: chain.id,
1255
- network: networkName,
1256
- records: deployer.verificationRecords,
1257
- };
1258
- await writeFile(verificationOutputPath, JSON.stringify(verificationData, null, 2));
1259
- logger.info(`Wrote L1 verification data to ${verificationOutputPath}`);
1260
- } catch (e) {
1261
- logger.warn(`Failed to write L1 verification data file: ${String(e)}`);
1262
- }
1512
+ await generateRollupVerificationRecords(rollup, deployer, args, l1Contracts, l1Client, logger);
1513
+ await writeVerificationJson(deployer, createVerificationJson, chain.id, '', logger);
1263
1514
  }
1264
1515
 
1265
1516
  if (isAnvilTestChain(chain.id)) {
@@ -1269,13 +1520,13 @@ export const deployL1Contracts = async (
1269
1520
  // Need to get the time
1270
1521
  const currentSlot = await rollup.getSlotNumber();
1271
1522
 
1272
- if (BigInt(currentSlot) === 0n) {
1273
- const ts = Number(await rollup.getTimestampForSlot(1n));
1523
+ if (currentSlot === 0) {
1524
+ const ts = Number(await rollup.getTimestampForSlot(SlotNumber(1)));
1274
1525
  await rpcCall('evm_setNextBlockTimestamp', [ts]);
1275
1526
  await rpcCall('hardhat_mine', [1]);
1276
1527
  const currentSlot = await rollup.getSlotNumber();
1277
1528
 
1278
- if (BigInt(currentSlot) !== 1n) {
1529
+ if (currentSlot !== 1) {
1279
1530
  throw new Error(`Error jumping time: current slot is ${currentSlot}`);
1280
1531
  }
1281
1532
  logger.info(`Jumped to slot 1`);
@@ -1295,6 +1546,7 @@ export const deployL1Contracts = async (
1295
1546
  stakingAssetHandlerAddress,
1296
1547
  zkPassportVerifierAddress,
1297
1548
  coinIssuerAddress,
1549
+ dateGatedRelayerAddress,
1298
1550
  },
1299
1551
  };
1300
1552
  };
@@ -1317,21 +1569,19 @@ export class L1Deployer {
1317
1569
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
1318
1570
  this.l1TxUtils = createL1TxUtilsFromViemWallet(
1319
1571
  this.client,
1320
- this.logger,
1321
- dateProvider,
1322
- this.txUtilsConfig,
1323
- this.acceleratedTestDeployments,
1572
+ { logger: this.logger, dateProvider },
1573
+ { ...this.txUtilsConfig, debugMaxGasLimit: acceleratedTestDeployments },
1324
1574
  );
1325
1575
  }
1326
1576
 
1327
1577
  async deploy<const TAbi extends Abi>(
1328
1578
  params: ContractArtifacts<TAbi>,
1329
1579
  args?: ContractConstructorArgs<TAbi>,
1330
- opts: { gasLimit?: bigint } = {},
1331
- ): Promise<EthAddress> {
1580
+ opts: { gasLimit?: bigint; noSimulation?: boolean } = {},
1581
+ ): Promise<{ address: EthAddress; existed: boolean }> {
1332
1582
  this.logger.debug(`Deploying ${params.name} contract`, { args });
1333
1583
  try {
1334
- const { txHash, address, deployedLibraries } = await deployL1Contract(
1584
+ const { txHash, address, deployedLibraries, existed } = await deployL1Contract(
1335
1585
  this.client,
1336
1586
  params.contractAbi,
1337
1587
  params.contractBytecode,
@@ -1343,6 +1593,7 @@ export class L1Deployer {
1343
1593
  l1TxUtils: this.l1TxUtils,
1344
1594
  acceleratedTestDeployments: this.acceleratedTestDeployments,
1345
1595
  gasLimit: opts.gasLimit,
1596
+ noSimulation: opts.noSimulation,
1346
1597
  },
1347
1598
  );
1348
1599
  if (txHash) {
@@ -1369,7 +1620,10 @@ export class L1Deployer {
1369
1620
  libraries: deployedLibraries ?? [],
1370
1621
  });
1371
1622
  }
1372
- return address;
1623
+ return {
1624
+ address,
1625
+ existed,
1626
+ };
1373
1627
  } catch (error) {
1374
1628
  throw new Error(`Failed to deploy ${params.name}`, { cause: formatViemError(error) });
1375
1629
  }
@@ -1397,13 +1651,16 @@ export class L1Deployer {
1397
1651
 
1398
1652
  sendTransaction(
1399
1653
  tx: L1TxRequest,
1400
- options?: L1GasConfig,
1654
+ options?: L1TxConfig,
1401
1655
  ): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
1402
- return this.l1TxUtils.sendTransaction(tx, options);
1656
+ return this.l1TxUtils.sendTransaction(tx, options).then(({ txHash, state }) => ({
1657
+ txHash,
1658
+ gasLimit: state.gasLimit,
1659
+ gasPrice: state.gasPrice,
1660
+ }));
1403
1661
  }
1404
1662
  }
1405
1663
 
1406
- // docs:start:deployL1Contract
1407
1664
  /**
1408
1665
  * Helper function to deploy ETH contracts.
1409
1666
  * @param walletClient - A viem WalletClient.
@@ -1426,18 +1683,28 @@ export async function deployL1Contract(
1426
1683
  l1TxUtils?: L1TxUtils;
1427
1684
  gasLimit?: bigint;
1428
1685
  acceleratedTestDeployments?: boolean;
1686
+ noSimulation?: boolean;
1429
1687
  } = {},
1430
- ): Promise<{ address: EthAddress; txHash: Hex | undefined; deployedLibraries?: VerificationLibraryEntry[] }> {
1688
+ ): Promise<{
1689
+ address: EthAddress;
1690
+ txHash: Hex | undefined;
1691
+ deployedLibraries?: VerificationLibraryEntry[];
1692
+ existed: boolean;
1693
+ }> {
1431
1694
  let txHash: Hex | undefined = undefined;
1432
1695
  let resultingAddress: Hex | null | undefined = undefined;
1433
1696
  const deployedLibraries: VerificationLibraryEntry[] = [];
1434
1697
 
1435
- const { salt: saltFromOpts, libraries, logger, gasLimit, acceleratedTestDeployments } = opts;
1698
+ const { salt: saltFromOpts, libraries, logger, gasLimit, acceleratedTestDeployments, noSimulation } = opts;
1436
1699
  let { l1TxUtils } = opts;
1437
1700
 
1438
1701
  if (!l1TxUtils) {
1439
1702
  const config = getL1TxUtilsConfigEnvVars();
1440
- l1TxUtils = createL1TxUtilsFromViemWallet(extendedClient, logger, undefined, config, acceleratedTestDeployments);
1703
+ l1TxUtils = createL1TxUtilsFromViemWallet(
1704
+ extendedClient,
1705
+ { logger },
1706
+ { ...config, debugMaxGasLimit: acceleratedTestDeployments },
1707
+ );
1441
1708
  }
1442
1709
 
1443
1710
  if (libraries) {
@@ -1529,17 +1796,21 @@ export async function deployL1Contract(
1529
1796
  }
1530
1797
  }
1531
1798
 
1799
+ let existed = false;
1800
+
1532
1801
  if (saltFromOpts) {
1533
1802
  logger?.info(`Deploying contract with salt ${saltFromOpts}`);
1534
1803
  const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, saltFromOpts);
1535
1804
  resultingAddress = address;
1536
1805
  const existing = await extendedClient.getCode({ address: resultingAddress });
1537
1806
  if (existing === undefined || existing === '0x') {
1538
- try {
1539
- await l1TxUtils.simulate({ to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]) }, { gasLimit });
1540
- } catch (err) {
1541
- logger?.error(`Failed to simulate deployment tx using universal deployer`, err);
1542
- await l1TxUtils.simulate({ to: null, data: encodeDeployData({ abi, bytecode, args }) });
1807
+ if (!noSimulation) {
1808
+ try {
1809
+ await l1TxUtils.simulate({ to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]), gas: gasLimit });
1810
+ } catch (err) {
1811
+ logger?.error(`Failed to simulate deployment tx using universal deployer`, err);
1812
+ await l1TxUtils.simulate({ to: null, data: encodeDeployData({ abi, bytecode, args }), gas: gasLimit });
1813
+ }
1543
1814
  }
1544
1815
  const res = await l1TxUtils.sendTransaction(
1545
1816
  { to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]) },
@@ -1550,13 +1821,17 @@ export async function deployL1Contract(
1550
1821
  logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
1551
1822
  } else {
1552
1823
  logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
1824
+ existed = true;
1553
1825
  }
1554
1826
  } else {
1555
1827
  const deployData = encodeDeployData({ abi, bytecode, args });
1556
- const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
1557
- to: null,
1558
- data: deployData,
1559
- });
1828
+ const { receipt } = await l1TxUtils.sendAndMonitorTransaction(
1829
+ {
1830
+ to: null,
1831
+ data: deployData,
1832
+ },
1833
+ { gasLimit },
1834
+ );
1560
1835
 
1561
1836
  txHash = receipt.transactionHash;
1562
1837
  resultingAddress = receipt.contractAddress;
@@ -1569,7 +1844,7 @@ export async function deployL1Contract(
1569
1844
  }
1570
1845
  }
1571
1846
 
1572
- return { address: EthAddress.fromString(resultingAddress!), txHash, deployedLibraries };
1847
+ return { address: EthAddress.fromString(resultingAddress!), txHash, deployedLibraries, existed };
1573
1848
  }
1574
1849
 
1575
1850
  export function getExpectedAddress(
@@ -1592,5 +1867,3 @@ export function getExpectedAddress(
1592
1867
  calldata,
1593
1868
  };
1594
1869
  }
1595
-
1596
- // docs:end:deployL1Contract