@aztec/ethereum 3.0.0-canary.a9708bd → 3.0.0-devnet.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 (144) hide show
  1. package/dest/client.d.ts +1 -1
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/config.d.ts +11 -6
  4. package/dest/config.d.ts.map +1 -1
  5. package/dest/config.js +124 -64
  6. package/dest/contracts/empire_base.d.ts +1 -1
  7. package/dest/contracts/empire_base.d.ts.map +1 -1
  8. package/dest/contracts/empire_slashing_proposer.d.ts +2 -2
  9. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  10. package/dest/contracts/empire_slashing_proposer.js +1 -1
  11. package/dest/contracts/fee_asset_handler.d.ts +3 -3
  12. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  13. package/dest/contracts/governance.js +7 -3
  14. package/dest/contracts/governance_proposer.d.ts +1 -2
  15. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  16. package/dest/contracts/governance_proposer.js +1 -2
  17. package/dest/contracts/multicall.d.ts +3 -5
  18. package/dest/contracts/multicall.d.ts.map +1 -1
  19. package/dest/contracts/multicall.js +6 -4
  20. package/dest/contracts/rollup.d.ts +39 -19
  21. package/dest/contracts/rollup.d.ts.map +1 -1
  22. package/dest/contracts/rollup.js +84 -88
  23. package/dest/contracts/slasher_contract.d.ts +10 -0
  24. package/dest/contracts/slasher_contract.d.ts.map +1 -1
  25. package/dest/contracts/slasher_contract.js +18 -0
  26. package/dest/contracts/tally_slashing_proposer.d.ts +22 -3
  27. package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -1
  28. package/dest/contracts/tally_slashing_proposer.js +55 -5
  29. package/dest/deploy_l1_contracts.d.ts +22 -7
  30. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  31. package/dest/deploy_l1_contracts.js +555 -362
  32. package/dest/index.d.ts +1 -1
  33. package/dest/index.d.ts.map +1 -1
  34. package/dest/index.js +1 -1
  35. package/dest/l1_artifacts.d.ts +8729 -6014
  36. package/dest/l1_artifacts.d.ts.map +1 -1
  37. package/dest/l1_artifacts.js +10 -5
  38. package/dest/l1_contract_addresses.d.ts +5 -1
  39. package/dest/l1_contract_addresses.d.ts.map +1 -1
  40. package/dest/l1_contract_addresses.js +16 -26
  41. package/dest/l1_reader.d.ts +1 -1
  42. package/dest/l1_reader.d.ts.map +1 -1
  43. package/dest/l1_reader.js +8 -8
  44. package/dest/l1_tx_utils/config.d.ts +59 -0
  45. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  46. package/dest/l1_tx_utils/config.js +73 -0
  47. package/dest/l1_tx_utils/constants.d.ts +6 -0
  48. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  49. package/dest/l1_tx_utils/constants.js +14 -0
  50. package/dest/l1_tx_utils/factory.d.ts +24 -0
  51. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  52. package/dest/l1_tx_utils/factory.js +12 -0
  53. package/dest/l1_tx_utils/index.d.ts +10 -0
  54. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  55. package/dest/l1_tx_utils/index.js +10 -0
  56. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  57. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  58. package/dest/l1_tx_utils/interfaces.js +4 -0
  59. package/dest/l1_tx_utils/l1_tx_utils.d.ts +95 -0
  60. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  61. package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
  62. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  63. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  64. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  65. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +81 -0
  66. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  67. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +294 -0
  68. package/dest/l1_tx_utils/signer.d.ts +4 -0
  69. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  70. package/dest/l1_tx_utils/signer.js +16 -0
  71. package/dest/l1_tx_utils/types.d.ts +67 -0
  72. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  73. package/dest/l1_tx_utils/types.js +26 -0
  74. package/dest/l1_tx_utils/utils.d.ts +4 -0
  75. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  76. package/dest/l1_tx_utils/utils.js +14 -0
  77. package/dest/publisher_manager.d.ts +7 -2
  78. package/dest/publisher_manager.d.ts.map +1 -1
  79. package/dest/publisher_manager.js +36 -8
  80. package/dest/queries.d.ts.map +1 -1
  81. package/dest/queries.js +11 -12
  82. package/dest/test/chain_monitor.d.ts +11 -0
  83. package/dest/test/chain_monitor.d.ts.map +1 -1
  84. package/dest/test/chain_monitor.js +81 -12
  85. package/dest/test/delayed_tx_utils.d.ts +2 -2
  86. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  87. package/dest/test/delayed_tx_utils.js +2 -2
  88. package/dest/test/eth_cheat_codes.d.ts +32 -6
  89. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  90. package/dest/test/eth_cheat_codes.js +115 -28
  91. package/dest/test/rollup_cheat_codes.d.ts +11 -9
  92. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  93. package/dest/test/rollup_cheat_codes.js +38 -6
  94. package/dest/test/upgrade_utils.d.ts.map +1 -1
  95. package/dest/test/upgrade_utils.js +3 -2
  96. package/dest/utils.d.ts.map +1 -1
  97. package/dest/utils.js +10 -161
  98. package/dest/zkPassportVerifierAddress.js +1 -1
  99. package/package.json +7 -7
  100. package/src/client.ts +1 -1
  101. package/src/config.ts +136 -68
  102. package/src/contracts/empire_base.ts +1 -1
  103. package/src/contracts/empire_slashing_proposer.ts +7 -3
  104. package/src/contracts/fee_asset_handler.ts +1 -1
  105. package/src/contracts/governance.ts +3 -3
  106. package/src/contracts/governance_proposer.ts +3 -4
  107. package/src/contracts/multicall.ts +12 -10
  108. package/src/contracts/rollup.ts +104 -106
  109. package/src/contracts/slasher_contract.ts +22 -0
  110. package/src/contracts/tally_slashing_proposer.ts +54 -6
  111. package/src/deploy_l1_contracts.ts +570 -328
  112. package/src/index.ts +1 -1
  113. package/src/l1_artifacts.ts +14 -6
  114. package/src/l1_contract_addresses.ts +17 -26
  115. package/src/l1_reader.ts +9 -9
  116. package/src/l1_tx_utils/README.md +177 -0
  117. package/src/l1_tx_utils/config.ts +140 -0
  118. package/src/l1_tx_utils/constants.ts +18 -0
  119. package/src/l1_tx_utils/factory.ts +64 -0
  120. package/src/l1_tx_utils/index.ts +12 -0
  121. package/src/l1_tx_utils/interfaces.ts +86 -0
  122. package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
  123. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  124. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +372 -0
  125. package/src/l1_tx_utils/signer.ts +28 -0
  126. package/src/l1_tx_utils/types.ts +85 -0
  127. package/src/l1_tx_utils/utils.ts +16 -0
  128. package/src/publisher_manager.ts +51 -9
  129. package/src/queries.ts +13 -8
  130. package/src/test/chain_monitor.ts +89 -9
  131. package/src/test/delayed_tx_utils.ts +2 -2
  132. package/src/test/eth_cheat_codes.ts +142 -29
  133. package/src/test/rollup_cheat_codes.ts +54 -14
  134. package/src/test/upgrade_utils.ts +3 -2
  135. package/src/utils.ts +13 -185
  136. package/src/zkPassportVerifierAddress.ts +1 -1
  137. package/dest/l1_tx_utils.d.ts +0 -250
  138. package/dest/l1_tx_utils.d.ts.map +0 -1
  139. package/dest/l1_tx_utils.js +0 -826
  140. package/dest/l1_tx_utils_with_blobs.d.ts +0 -19
  141. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  142. package/dest/l1_tx_utils_with_blobs.js +0 -85
  143. package/src/l1_tx_utils.ts +0 -1105
  144. package/src/l1_tx_utils_with_blobs.ts +0 -144
@@ -6,50 +6,145 @@ import { jsonStringify } from '@aztec/foundation/json-rpc';
6
6
  import { createLogger } from '@aztec/foundation/log';
7
7
  import { DateProvider } from '@aztec/foundation/timer';
8
8
  import { mkdir, writeFile } from 'fs/promises';
9
+ import chunk from 'lodash.chunk';
9
10
  import { concatHex, encodeAbiParameters, encodeDeployData, encodeFunctionData, getAddress, getContract, getContractAddress, numberToHex, padHex } from 'viem';
10
11
  import { foundry } from 'viem/chains';
11
12
  import { isAnvilTestChain } from './chain.js';
12
13
  import { createExtendedL1Client } from './client.js';
13
- import { getEntryQueueConfig, getGSEConfiguration, getGovernanceConfiguration, getRewardBoostConfig, getRewardConfig, validateConfig } from './config.js';
14
+ import { getEntryQueueConfig, getGovernanceConfiguration, getRewardBoostConfig, getRewardConfig, validateConfig } from './config.js';
14
15
  import { GSEContract } from './contracts/gse.js';
15
16
  import { deployMulticall3 } from './contracts/multicall.js';
16
17
  import { RegistryContract } from './contracts/registry.js';
17
18
  import { RollupContract, SlashingProposerType } from './contracts/rollup.js';
18
- import { CoinIssuerArtifact, FeeAssetArtifact, FeeAssetHandlerArtifact, GSEArtifact, GovernanceArtifact, GovernanceProposerArtifact, MultiAdderArtifact, RegisterNewRollupVersionPayloadArtifact, RegistryArtifact, RollupArtifact, SlashFactoryArtifact, StakingAssetArtifact, StakingAssetHandlerArtifact, l1ArtifactsVerifiers, mockVerifiers } from './l1_artifacts.js';
19
- import { createL1TxUtilsFromViemWallet, getL1TxUtilsConfigEnvVars } from './l1_tx_utils.js';
19
+ import { CoinIssuerArtifact, DateGatedRelayerArtifact, FeeAssetArtifact, FeeAssetHandlerArtifact, GSEArtifact, GovernanceArtifact, GovernanceProposerArtifact, MultiAdderArtifact, RegisterNewRollupVersionPayloadArtifact, RegistryArtifact, RollupArtifact, SlashFactoryArtifact, StakingAssetArtifact, StakingAssetHandlerArtifact, l1ArtifactsVerifiers, mockVerifiers } from './l1_artifacts.js';
20
+ import { createL1TxUtilsFromViemWallet, getL1TxUtilsConfigEnvVars } from './l1_tx_utils/index.js';
20
21
  import { formatViemError } from './utils.js';
21
22
  import { ZK_PASSPORT_DOMAIN, ZK_PASSPORT_SCOPE, ZK_PASSPORT_VERIFIER_ADDRESS } from './zkPassportVerifierAddress.js';
22
23
  export const DEPLOYER_ADDRESS = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
23
- const networkName = getActiveNetworkName();
24
+ // Minimal ERC20 ABI for validation purposes. We only read view methods.
25
+ const ERC20_VALIDATION_ABI = [
26
+ {
27
+ type: 'function',
28
+ name: 'totalSupply',
29
+ stateMutability: 'view',
30
+ inputs: [],
31
+ outputs: [
32
+ {
33
+ name: '',
34
+ type: 'uint256'
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ type: 'function',
40
+ name: 'name',
41
+ stateMutability: 'view',
42
+ inputs: [],
43
+ outputs: [
44
+ {
45
+ name: '',
46
+ type: 'string'
47
+ }
48
+ ]
49
+ },
50
+ {
51
+ type: 'function',
52
+ name: 'symbol',
53
+ stateMutability: 'view',
54
+ inputs: [],
55
+ outputs: [
56
+ {
57
+ name: '',
58
+ type: 'string'
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ type: 'function',
64
+ name: 'decimals',
65
+ stateMutability: 'view',
66
+ inputs: [],
67
+ outputs: [
68
+ {
69
+ name: '',
70
+ type: 'uint8'
71
+ }
72
+ ]
73
+ }
74
+ ];
75
+ /**
76
+ * Validates that the provided address points to a contract that resembles an ERC20 token.
77
+ * Checks for contract code and attempts common ERC20 view calls.
78
+ * Throws an error if validation fails.
79
+ */ export async function validateExistingErc20TokenAddress(l1Client, tokenAddress, logger) {
80
+ const addressString = tokenAddress.toString();
81
+ // Ensure there is contract code at the address
82
+ const code = await l1Client.getCode({
83
+ address: addressString
84
+ });
85
+ if (!code || code === '0x') {
86
+ throw new Error(`No contract code found at provided token address ${addressString}`);
87
+ }
88
+ const contract = getContract({
89
+ address: getAddress(addressString),
90
+ abi: ERC20_VALIDATION_ABI,
91
+ client: l1Client
92
+ });
93
+ // Validate all required ERC20 methods in parallel
94
+ const checks = [
95
+ contract.read.totalSupply().then((total)=>typeof total === 'bigint'),
96
+ contract.read.name().then(()=>true),
97
+ contract.read.symbol().then(()=>true),
98
+ contract.read.decimals().then((dec)=>typeof dec === 'number' || typeof dec === 'bigint')
99
+ ];
100
+ const results = await Promise.allSettled(checks);
101
+ const failedChecks = results.filter((result)=>result.status === 'rejected' || result.value !== true);
102
+ if (failedChecks.length > 0) {
103
+ throw new Error(`Address ${addressString} does not appear to implement ERC20 view methods`);
104
+ }
105
+ logger.verbose(`Validated existing token at ${addressString} appears to be ERC20-compatible`);
106
+ }
24
107
  export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
25
- logger.info(`Deploying shared contracts for network configration: ${networkName}`);
108
+ const networkName = getActiveNetworkName();
109
+ logger.info(`Deploying shared contracts for network configuration: ${networkName}`);
26
110
  const txHashes = [];
27
- const feeAssetAddress = await deployer.deploy(FeeAssetArtifact, [
28
- 'FeeJuice',
29
- 'FEE',
30
- l1Client.account.address
31
- ]);
32
- logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
33
- const stakingAssetAddress = await deployer.deploy(StakingAssetArtifact, [
34
- 'Staking',
35
- 'STK',
36
- l1Client.account.address
37
- ]);
38
- logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
39
- const gseConfiguration = getGSEConfiguration(networkName);
40
- const gseAddress = await deployer.deploy(GSEArtifact, [
111
+ let feeAssetAddress;
112
+ let stakingAssetAddress;
113
+ if (args.existingTokenAddress) {
114
+ await validateExistingErc20TokenAddress(l1Client, args.existingTokenAddress, logger);
115
+ feeAssetAddress = args.existingTokenAddress;
116
+ stakingAssetAddress = args.existingTokenAddress;
117
+ logger.verbose(`Using existing token for fee and staking assets at ${args.existingTokenAddress}`);
118
+ } else {
119
+ const deployedFee = await deployer.deploy(FeeAssetArtifact, [
120
+ 'FeeJuice',
121
+ 'FEE',
122
+ l1Client.account.address
123
+ ]);
124
+ feeAssetAddress = deployedFee.address;
125
+ logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
126
+ const deployedStaking = await deployer.deploy(StakingAssetArtifact, [
127
+ 'Staking',
128
+ 'STK',
129
+ l1Client.account.address
130
+ ]);
131
+ stakingAssetAddress = deployedStaking.address;
132
+ logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
133
+ await deployer.waitForDeployments();
134
+ }
135
+ const gseAddress = (await deployer.deploy(GSEArtifact, [
41
136
  l1Client.account.address,
42
137
  stakingAssetAddress.toString(),
43
- gseConfiguration.activationThreshold,
44
- gseConfiguration.ejectionThreshold
45
- ]);
138
+ args.activationThreshold,
139
+ args.ejectionThreshold
140
+ ])).address;
46
141
  logger.verbose(`Deployed GSE at ${gseAddress}`);
47
- const registryAddress = await deployer.deploy(RegistryArtifact, [
142
+ const { address: registryAddress } = await deployer.deploy(RegistryArtifact, [
48
143
  l1Client.account.address,
49
144
  feeAssetAddress.toString()
50
145
  ]);
51
146
  logger.verbose(`Deployed Registry at ${registryAddress}`);
52
- const governanceProposerAddress = await deployer.deploy(GovernanceProposerArtifact, [
147
+ const { address: governanceProposerAddress } = await deployer.deploy(GovernanceProposerArtifact, [
53
148
  registryAddress.toString(),
54
149
  gseAddress.toString(),
55
150
  BigInt(args.governanceProposerQuorum ?? args.governanceProposerRoundSize / 2 + 1),
@@ -58,7 +153,7 @@ export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
58
153
  logger.verbose(`Deployed GovernanceProposer at ${governanceProposerAddress}`);
59
154
  // @note @LHerskind the assets are expected to be the same at some point, but for better
60
155
  // configurability they are different for now.
61
- const governanceAddress = await deployer.deploy(GovernanceArtifact, [
156
+ const { address: governanceAddress } = await deployer.deploy(GovernanceArtifact, [
62
157
  stakingAssetAddress.toString(),
63
158
  governanceProposerAddress.toString(),
64
159
  gseAddress.toString(),
@@ -98,11 +193,11 @@ export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
98
193
  logger.verbose(`Set governance on GSE in ${txHash}`);
99
194
  txHashes.push(txHash);
100
195
  }
101
- const coinIssuerAddress = await deployer.deploy(CoinIssuerArtifact, [
196
+ const coinIssuerAddress = (await deployer.deploy(CoinIssuerArtifact, [
102
197
  feeAssetAddress.toString(),
103
- 1_000_000n * 10n ** 18n,
198
+ 2n * 10n ** 17n,
104
199
  l1Client.account.address
105
- ]);
200
+ ])).address;
106
201
  logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
107
202
  logger.verbose(`Waiting for deployments to complete`);
108
203
  await deployer.waitForDeployments();
@@ -110,13 +205,13 @@ export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
110
205
  let feeAssetHandlerAddress = undefined;
111
206
  let stakingAssetHandlerAddress = undefined;
112
207
  let zkPassportVerifierAddress = undefined;
113
- // Only if not on mainnet will we deploy the handlers
114
- if (l1Client.chain.id !== 1) {
115
- /* -------------------------------------------------------------------------- */ /* CHEAT CODES START HERE */ /* -------------------------------------------------------------------------- */ feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
208
+ // Only if not on mainnet will we deploy the handlers, and only when we control the token
209
+ if (l1Client.chain.id !== 1 && !args.existingTokenAddress) {
210
+ /* -------------------------------------------------------------------------- */ /* CHEAT CODES START HERE */ /* -------------------------------------------------------------------------- */ feeAssetHandlerAddress = (await deployer.deploy(FeeAssetHandlerArtifact, [
116
211
  l1Client.account.address,
117
212
  feeAssetAddress.toString(),
118
213
  BigInt(1000n * 10n ** 18n)
119
- ]);
214
+ ])).address;
120
215
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
121
216
  // Only if we are "fresh" will we be adding as a minter, otherwise above will simply get same address
122
217
  if (needToSetGovernance) {
@@ -147,6 +242,7 @@ export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
147
242
  stakingAsset: stakingAssetAddress.toString(),
148
243
  registry: registryAddress.toString(),
149
244
  withdrawer: AMIN.toString(),
245
+ validatorsToFlush: 16n,
150
246
  mintInterval: BigInt(60 * 60 * 24),
151
247
  depositsPerMint: BigInt(10),
152
248
  depositMerkleRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
@@ -161,9 +257,9 @@ export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
161
257
  skipBindCheck: args.zkPassportArgs?.mockZkPassportVerifier ?? false,
162
258
  skipMerkleCheck: true
163
259
  };
164
- stakingAssetHandlerAddress = await deployer.deploy(StakingAssetHandlerArtifact, [
260
+ stakingAssetHandlerAddress = (await deployer.deploy(StakingAssetHandlerArtifact, [
165
261
  stakingAssetHandlerDeployArgs
166
- ]);
262
+ ])).address;
167
263
  logger.verbose(`Deployed StakingAssetHandler at ${stakingAssetHandlerAddress}`);
168
264
  const { txHash: stakingMinterTxHash } = await deployer.sendTransaction({
169
265
  to: stakingAssetAddress.toString(),
@@ -187,20 +283,24 @@ export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
187
283
  logger.verbose(`Deployed shared contracts`);
188
284
  const registry = new RegistryContract(l1Client, registryAddress);
189
285
  /* -------------------------------------------------------------------------- */ /* FUND REWARD DISTRIBUTOR START */ /* -------------------------------------------------------------------------- */ const rewardDistributorAddress = await registry.getRewardDistributor();
190
- const blockReward = getRewardConfig(networkName).blockReward;
191
- const funding = blockReward * 200000n;
192
- const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
193
- to: feeAssetAddress.toString(),
194
- data: encodeFunctionData({
195
- abi: FeeAssetArtifact.contractAbi,
196
- functionName: 'mint',
197
- args: [
198
- rewardDistributorAddress.toString(),
199
- funding
200
- ]
201
- })
202
- });
203
- logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
286
+ if (!args.existingTokenAddress) {
287
+ const blockReward = getRewardConfig(networkName).blockReward;
288
+ const funding = blockReward * 200000n;
289
+ const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
290
+ to: feeAssetAddress.toString(),
291
+ data: encodeFunctionData({
292
+ abi: FeeAssetArtifact.contractAbi,
293
+ functionName: 'mint',
294
+ args: [
295
+ rewardDistributorAddress.toString(),
296
+ funding
297
+ ]
298
+ })
299
+ });
300
+ logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
301
+ } else {
302
+ logger.verbose(`Skipping reward distributor funding as existing token is provided`);
303
+ }
204
304
  /* -------------------------------------------------------------------------- */ /* FUND REWARD DISTRIBUTOR STOP */ /* -------------------------------------------------------------------------- */ return {
205
305
  feeAssetAddress,
206
306
  feeAssetHandlerAddress,
@@ -217,7 +317,7 @@ export const deploySharedContracts = async (l1Client, deployer, args, logger)=>{
217
317
  };
218
318
  const getZkPassportVerifierAddress = async (deployer, args)=>{
219
319
  if (args.zkPassportArgs?.mockZkPassportVerifier) {
220
- return await deployer.deploy(mockVerifiers.mockZkPassportVerifier);
320
+ return (await deployer.deploy(mockVerifiers.mockZkPassportVerifier)).address;
221
321
  }
222
322
  return ZK_PASSPORT_VERIFIER_ADDRESS;
223
323
  };
@@ -233,6 +333,224 @@ const getZkPassportVerifierAddress = async (deployer, args)=>{
233
333
  scope
234
334
  ];
235
335
  };
336
+ /**
337
+ * Generates verification records for a deployed rollup and its associated contracts (Inbox, Outbox, Slasher, etc).
338
+ * @param rollup - The deployed rollup contract.
339
+ * @param deployer - The L1 deployer instance.
340
+ * @param args - The deployment arguments used for the rollup.
341
+ * @param addresses - The L1 contract addresses.
342
+ * @param extendedClient - The extended viem wallet client.
343
+ * @param logger - The logger.
344
+ */ async function generateRollupVerificationRecords(rollup, deployer, args, addresses, extendedClient, logger) {
345
+ try {
346
+ // Add Inbox / Outbox verification records (constructor args are created inside RollupCore)
347
+ const rollupAddr = rollup.address;
348
+ const rollupAddresses = await rollup.getRollupAddresses();
349
+ const inboxAddr = rollupAddresses.inboxAddress.toString();
350
+ const outboxAddr = rollupAddresses.outboxAddress.toString();
351
+ const feeAsset = rollupAddresses.feeJuiceAddress.toString();
352
+ const version = await rollup.getVersion();
353
+ const inboxCtor = encodeAbiParameters([
354
+ {
355
+ type: 'address'
356
+ },
357
+ {
358
+ type: 'address'
359
+ },
360
+ {
361
+ type: 'uint256'
362
+ },
363
+ {
364
+ type: 'uint256'
365
+ }
366
+ ], [
367
+ rollupAddr,
368
+ feeAsset,
369
+ version,
370
+ BigInt(L1_TO_L2_MSG_SUBTREE_HEIGHT)
371
+ ]);
372
+ const outboxCtor = encodeAbiParameters([
373
+ {
374
+ type: 'address'
375
+ },
376
+ {
377
+ type: 'uint256'
378
+ }
379
+ ], [
380
+ rollupAddr,
381
+ version
382
+ ]);
383
+ deployer.verificationRecords.push({
384
+ name: 'Inbox',
385
+ address: inboxAddr,
386
+ constructorArgsHex: inboxCtor,
387
+ libraries: []
388
+ }, {
389
+ name: 'Outbox',
390
+ address: outboxAddr,
391
+ constructorArgsHex: outboxCtor,
392
+ libraries: []
393
+ });
394
+ // Include Slasher and SlashingProposer (if deployed) in verification data
395
+ try {
396
+ const slasherAddrHex = await rollup.getSlasherAddress();
397
+ const slasherAddr = EthAddress.fromString(slasherAddrHex);
398
+ if (!slasherAddr.isZero()) {
399
+ // Slasher constructor: (address _vetoer, address _governance)
400
+ const slasherCtor = encodeAbiParameters([
401
+ {
402
+ type: 'address'
403
+ },
404
+ {
405
+ type: 'address'
406
+ }
407
+ ], [
408
+ args.slashingVetoer.toString(),
409
+ extendedClient.account.address
410
+ ]);
411
+ deployer.verificationRecords.push({
412
+ name: 'Slasher',
413
+ address: slasherAddr.toString(),
414
+ constructorArgsHex: slasherCtor,
415
+ libraries: []
416
+ });
417
+ // Proposer address is stored in Slasher.PROPOSER()
418
+ const proposerAddr = (await rollup.getSlashingProposerAddress()).toString();
419
+ // Compute constructor args matching deployment path in RollupCore
420
+ const computedRoundSize = BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration);
421
+ const computedQuorum = BigInt(args.slashingQuorum ?? args.slashingRoundSizeInEpochs * args.aztecEpochDuration / 2 + 1);
422
+ const lifetimeInRounds = BigInt(args.slashingLifetimeInRounds);
423
+ const executionDelayInRounds = BigInt(args.slashingExecutionDelayInRounds);
424
+ if (args.slasherFlavor === 'tally') {
425
+ const slashAmounts = [
426
+ args.slashAmountSmall,
427
+ args.slashAmountMedium,
428
+ args.slashAmountLarge
429
+ ];
430
+ const committeeSize = BigInt(args.aztecTargetCommitteeSize);
431
+ const epochDuration = BigInt(args.aztecEpochDuration);
432
+ const slashOffsetInRounds = BigInt(args.slashingOffsetInRounds);
433
+ const proposerCtor = encodeAbiParameters([
434
+ {
435
+ type: 'address'
436
+ },
437
+ {
438
+ type: 'address'
439
+ },
440
+ {
441
+ type: 'uint256'
442
+ },
443
+ {
444
+ type: 'uint256'
445
+ },
446
+ {
447
+ type: 'uint256'
448
+ },
449
+ {
450
+ type: 'uint256'
451
+ },
452
+ {
453
+ type: 'uint256[3]'
454
+ },
455
+ {
456
+ type: 'uint256'
457
+ },
458
+ {
459
+ type: 'uint256'
460
+ },
461
+ {
462
+ type: 'uint256'
463
+ }
464
+ ], [
465
+ rollup.address,
466
+ slasherAddr.toString(),
467
+ computedQuorum,
468
+ computedRoundSize,
469
+ lifetimeInRounds,
470
+ executionDelayInRounds,
471
+ slashAmounts,
472
+ committeeSize,
473
+ epochDuration,
474
+ slashOffsetInRounds
475
+ ]);
476
+ deployer.verificationRecords.push({
477
+ name: 'TallySlashingProposer',
478
+ address: proposerAddr,
479
+ constructorArgsHex: proposerCtor,
480
+ libraries: []
481
+ });
482
+ } else if (args.slasherFlavor === 'empire') {
483
+ const proposerCtor = encodeAbiParameters([
484
+ {
485
+ type: 'address'
486
+ },
487
+ {
488
+ type: 'address'
489
+ },
490
+ {
491
+ type: 'uint256'
492
+ },
493
+ {
494
+ type: 'uint256'
495
+ },
496
+ {
497
+ type: 'uint256'
498
+ },
499
+ {
500
+ type: 'uint256'
501
+ }
502
+ ], [
503
+ rollup.address,
504
+ slasherAddr.toString(),
505
+ computedQuorum,
506
+ computedRoundSize,
507
+ lifetimeInRounds,
508
+ executionDelayInRounds
509
+ ]);
510
+ deployer.verificationRecords.push({
511
+ name: 'EmpireSlashingProposer',
512
+ address: proposerAddr,
513
+ constructorArgsHex: proposerCtor,
514
+ libraries: []
515
+ });
516
+ }
517
+ }
518
+ } catch (e) {
519
+ logger.warn(`Failed to add Slasher/Proposer verification records: ${String(e)}`);
520
+ }
521
+ } catch (e) {
522
+ throw new Error(`Failed to generate rollup verification records: ${String(e)}`);
523
+ }
524
+ }
525
+ /**
526
+ * Writes verification records to a JSON file for later forge verify.
527
+ * @param deployer - The L1 deployer containing verification records.
528
+ * @param outputDirectory - The directory to write the verification file to.
529
+ * @param chainId - The chain ID.
530
+ * @param filenameSuffix - Optional suffix for the filename (e.g., 'upgrade').
531
+ * @param logger - The logger.
532
+ */ async function writeVerificationJson(deployer, outputDirectory, chainId, filenameSuffix = '', logger) {
533
+ try {
534
+ const date = new Date();
535
+ const formattedDate = date.toISOString().slice(2, 19).replace(/[-T:]/g, '');
536
+ // Ensure the verification output directory exists
537
+ await mkdir(outputDirectory, {
538
+ recursive: true
539
+ });
540
+ const suffix = filenameSuffix ? `-${filenameSuffix}` : '';
541
+ const verificationOutputPath = `${outputDirectory}/l1-verify${suffix}-${chainId}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
542
+ const networkName = getActiveNetworkName();
543
+ const verificationData = {
544
+ chainId: chainId,
545
+ network: networkName,
546
+ records: deployer.verificationRecords
547
+ };
548
+ await writeFile(verificationOutputPath, JSON.stringify(verificationData, null, 2));
549
+ logger.info(`Wrote L1 verification data to ${verificationOutputPath}`);
550
+ } catch (e) {
551
+ logger.warn(`Failed to write L1 verification data file: ${String(e)}`);
552
+ }
553
+ }
236
554
  /**
237
555
  * Deploys a new rollup, using the existing canonical version to derive certain values (addresses of assets etc).
238
556
  * @param clients - The L1 clients.
@@ -240,28 +558,34 @@ const getZkPassportVerifierAddress = async (deployer, args)=>{
240
558
  * @param registryAddress - The address of the registry.
241
559
  * @param logger - The logger.
242
560
  * @param txUtilsConfig - The L1 tx utils config.
243
- */ export const deployRollupForUpgrade = async (extendedClient, args, registryAddress, logger, txUtilsConfig)=>{
244
- const deployer = new L1Deployer(extendedClient, args.salt, undefined, args.acceleratedTestDeployments, logger, txUtilsConfig);
561
+ * @param createVerificationJson - Optional path to write verification data for forge verify.
562
+ */ export const deployRollupForUpgrade = async (extendedClient, args, registryAddress, logger, txUtilsConfig, createVerificationJson = false)=>{
563
+ const deployer = new L1Deployer(extendedClient, args.salt, undefined, args.acceleratedTestDeployments, logger, txUtilsConfig, !!createVerificationJson);
245
564
  const addresses = await RegistryContract.collectAddresses(extendedClient, registryAddress, 'canonical');
246
565
  const { rollup, slashFactoryAddress } = await deployRollup(extendedClient, deployer, args, addresses, logger);
247
566
  await deployer.waitForDeployments();
567
+ // Write verification data (constructor args + linked libraries) to file for later forge verify
568
+ if (createVerificationJson) {
569
+ await generateRollupVerificationRecords(rollup, deployer, args, addresses, extendedClient, logger);
570
+ await writeVerificationJson(deployer, createVerificationJson, extendedClient.chain.id, 'upgrade', logger);
571
+ }
248
572
  return {
249
573
  rollup,
250
574
  slashFactoryAddress
251
575
  };
252
576
  };
253
577
  export const deploySlashFactory = async (deployer, rollupAddress, logger)=>{
254
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [
578
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [
255
579
  rollupAddress
256
- ]);
580
+ ])).address;
257
581
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
258
582
  return slashFactoryAddress;
259
583
  };
260
584
  export const deployUpgradePayload = async (deployer, addresses)=>{
261
- const payloadAddress = await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
585
+ const payloadAddress = (await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
262
586
  addresses.registryAddress.toString(),
263
587
  addresses.rollupAddress.toString()
264
- ]);
588
+ ])).address;
265
589
  return payloadAddress;
266
590
  };
267
591
  function slasherFlavorToSolidityEnum(flavor) {
@@ -285,14 +609,15 @@ function slasherFlavorToSolidityEnum(flavor) {
285
609
  if (!addresses.gseAddress) {
286
610
  throw new Error('GSE address is required when deploying');
287
611
  }
612
+ const networkName = getActiveNetworkName();
288
613
  logger.info(`Deploying rollup using network configuration: ${networkName}`);
289
614
  const txHashes = [];
290
615
  let epochProofVerifier = EthAddress.ZERO;
291
616
  if (args.realVerifier) {
292
- epochProofVerifier = await deployer.deploy(l1ArtifactsVerifiers.honkVerifier);
617
+ epochProofVerifier = (await deployer.deploy(l1ArtifactsVerifiers.honkVerifier)).address;
293
618
  logger.verbose(`Rollup will use the real verifier at ${epochProofVerifier}`);
294
619
  } else {
295
- epochProofVerifier = await deployer.deploy(mockVerifiers.mockVerifier);
620
+ epochProofVerifier = (await deployer.deploy(mockVerifiers.mockVerifier)).address;
296
621
  logger.verbose(`Rollup will use the mock verifier at ${epochProofVerifier}`);
297
622
  }
298
623
  const rewardConfig = {
@@ -303,6 +628,7 @@ function slasherFlavorToSolidityEnum(flavor) {
303
628
  aztecSlotDuration: BigInt(args.aztecSlotDuration),
304
629
  aztecEpochDuration: BigInt(args.aztecEpochDuration),
305
630
  targetCommitteeSize: BigInt(args.aztecTargetCommitteeSize),
631
+ lagInEpochs: BigInt(args.lagInEpochs),
306
632
  aztecProofSubmissionEpochs: BigInt(args.aztecProofSubmissionEpochs),
307
633
  slashingQuorum: BigInt(args.slashingQuorum ?? args.slashingRoundSizeInEpochs * args.aztecEpochDuration / 2 + 1),
308
634
  slashingRoundSize: BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration),
@@ -313,7 +639,7 @@ function slasherFlavorToSolidityEnum(flavor) {
313
639
  provingCostPerMana: args.provingCostPerMana,
314
640
  rewardConfig: rewardConfig,
315
641
  version: 0,
316
- rewardBoostConfig: getRewardBoostConfig(networkName),
642
+ rewardBoostConfig: getRewardBoostConfig(),
317
643
  stakingQueueConfig: getEntryQueueConfig(networkName),
318
644
  exitDelaySeconds: BigInt(args.exitDelaySeconds),
319
645
  slasherFlavor: slasherFlavorToSolidityEnum(args.slasherFlavor),
@@ -322,11 +648,14 @@ function slasherFlavorToSolidityEnum(flavor) {
322
648
  args.slashAmountSmall,
323
649
  args.slashAmountMedium,
324
650
  args.slashAmountLarge
325
- ]
651
+ ],
652
+ localEjectionThreshold: args.localEjectionThreshold,
653
+ slashingDisableDuration: BigInt(args.slashingDisableDuration ?? 0n),
654
+ earliestRewardsClaimableTimestamp: 0n
326
655
  };
327
656
  const genesisStateArgs = {
328
657
  vkTreeRoot: args.vkTreeRoot.toString(),
329
- protocolContractTreeRoot: args.protocolContractTreeRoot.toString(),
658
+ protocolContractsHash: args.protocolContractsHash.toString(),
330
659
  genesisArchiveRoot: args.genesisArchiveRoot.toString()
331
660
  };
332
661
  // Until there is an actual chain-id for the version, we will just draw a random value.
@@ -345,33 +674,38 @@ function slasherFlavorToSolidityEnum(flavor) {
345
674
  genesisStateArgs,
346
675
  rollupConfigArgs
347
676
  ];
348
- const rollupAddress = await deployer.deploy(RollupArtifact, rollupArgs, {
677
+ const { address: rollupAddress, existed: rollupExisted } = await deployer.deploy(RollupArtifact, rollupArgs, {
349
678
  gasLimit: 15_000_000n
350
679
  });
351
- logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
680
+ logger.verbose(`Deployed Rollup at ${rollupAddress}, already existed: ${rollupExisted}`, rollupConfigArgs);
352
681
  const rollupContract = new RollupContract(extendedClient, rollupAddress);
353
682
  await deployer.waitForDeployments();
354
683
  logger.verbose(`All core contracts have been deployed`);
355
684
  if (args.feeJuicePortalInitialBalance && args.feeJuicePortalInitialBalance > 0n) {
356
- const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
357
- // In fast mode, use the L1TxUtils to send transactions with nonce management
358
- const { txHash: mintTxHash } = await deployer.sendTransaction({
359
- to: addresses.feeJuiceAddress.toString(),
360
- data: encodeFunctionData({
361
- abi: FeeAssetArtifact.contractAbi,
362
- functionName: 'mint',
363
- args: [
364
- feeJuicePortalAddress.toString(),
365
- args.feeJuicePortalInitialBalance
366
- ]
367
- })
368
- });
369
- logger.verbose(`Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`);
370
- txHashes.push(mintTxHash);
685
+ // Skip funding when using an external token, as we likely don't have mint permissions
686
+ if (!('existingTokenAddress' in args) || !args.existingTokenAddress) {
687
+ const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
688
+ // In fast mode, use the L1TxUtils to send transactions with nonce management
689
+ const { txHash: mintTxHash } = await deployer.sendTransaction({
690
+ to: addresses.feeJuiceAddress.toString(),
691
+ data: encodeFunctionData({
692
+ abi: FeeAssetArtifact.contractAbi,
693
+ functionName: 'mint',
694
+ args: [
695
+ feeJuicePortalAddress.toString(),
696
+ args.feeJuicePortalInitialBalance
697
+ ]
698
+ })
699
+ });
700
+ logger.verbose(`Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`);
701
+ txHashes.push(mintTxHash);
702
+ } else {
703
+ logger.verbose('Skipping fee juice portal funding due to external token usage');
704
+ }
371
705
  }
372
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [
706
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [
373
707
  rollupAddress.toString()
374
- ]);
708
+ ])).address;
375
709
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
376
710
  // We need to call a function on the registry to set the various contract addresses.
377
711
  const registryContract = getContract({
@@ -435,7 +769,11 @@ function slasherFlavorToSolidityEnum(flavor) {
435
769
  } else {
436
770
  logger.verbose(`Not the owner of the gse, skipping rollup addition`);
437
771
  }
438
- if (args.initialValidators && await gseContract.read.isRollupRegistered([
772
+ const activeAttestorCount = await rollupContract.getActiveAttesterCount();
773
+ const queuedAttestorCount = await rollupContract.getEntryQueueLength();
774
+ logger.info(`Rollup has ${activeAttestorCount} active attestors and ${queuedAttestorCount} queued attestors`);
775
+ const shouldAddValidators = activeAttestorCount === 0n && queuedAttestorCount === 0n;
776
+ if (args.initialValidators && shouldAddValidators && await gseContract.read.isRollupRegistered([
439
777
  rollupContract.address
440
778
  ])) {
441
779
  await addMultipleValidators(extendedClient, deployer, addresses.gseAddress.toString(), rollupAddress.toString(), addresses.stakingAssetAddress.toString(), args.initialValidators, args.acceleratedTestDeployments, logger);
@@ -467,7 +805,7 @@ function slasherFlavorToSolidityEnum(flavor) {
467
805
  slashFactoryAddress
468
806
  };
469
807
  };
470
- export const handoverToGovernance = async (extendedClient, deployer, registryAddress, gseAddress, coinIssuerAddress, feeAssetAddress, governanceAddress, logger, acceleratedTestDeployments)=>{
808
+ export const handoverToGovernance = async (extendedClient, deployer, registryAddress, gseAddress, coinIssuerAddress, feeAssetAddress, governanceAddress, logger, acceleratedTestDeployments, useExternalToken = false)=>{
471
809
  // We need to call a function on the registry to set the various contract addresses.
472
810
  const registryContract = getContract({
473
811
  address: getAddress(registryAddress.toString()),
@@ -522,7 +860,7 @@ export const handoverToGovernance = async (extendedClient, deployer, registryAdd
522
860
  logger.verbose(`Transferring the ownership of the gse contract at ${gseAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`);
523
861
  txHashes.push(transferOwnershipTxHash);
524
862
  }
525
- if (acceleratedTestDeployments || await feeAsset.read.owner() !== coinIssuerAddress.toString()) {
863
+ if (!useExternalToken && (acceleratedTestDeployments || await feeAsset.read.owner() !== coinIssuerAddress.toString())) {
526
864
  const { txHash } = await deployer.sendTransaction({
527
865
  to: feeAssetAddress.toString(),
528
866
  data: encodeFunctionData({
@@ -548,20 +886,27 @@ export const handoverToGovernance = async (extendedClient, deployer, registryAdd
548
886
  });
549
887
  logger.verbose(`Accept ownership of fee asset in ${acceptTokenOwnershipTxHash}`);
550
888
  txHashes.push(acceptTokenOwnershipTxHash);
889
+ } else if (useExternalToken) {
890
+ logger.verbose('Skipping fee asset ownership transfer due to external token usage');
551
891
  }
892
+ // Either deploy or at least predict the address of the date gated relayer
893
+ const dateGatedRelayer = await deployer.deploy(DateGatedRelayerArtifact, [
894
+ governanceAddress.toString(),
895
+ 1798761600n
896
+ ]);
552
897
  // If the owner is not the Governance contract, transfer ownership to the Governance contract
553
- if (acceleratedTestDeployments || await coinIssuerContract.read.owner() !== getAddress(governanceAddress.toString())) {
898
+ if (acceleratedTestDeployments || await coinIssuerContract.read.owner() === deployer.client.account.address) {
554
899
  const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
555
900
  to: coinIssuerContract.address,
556
901
  data: encodeFunctionData({
557
902
  abi: CoinIssuerArtifact.contractAbi,
558
903
  functionName: 'transferOwnership',
559
904
  args: [
560
- getAddress(governanceAddress.toString())
905
+ getAddress(dateGatedRelayer.address.toString())
561
906
  ]
562
907
  })
563
908
  });
564
- logger.verbose(`Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`);
909
+ logger.verbose(`Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the DateGatedRelayer ${dateGatedRelayer.address} in tx ${transferOwnershipTxHash}`);
565
910
  txHashes.push(transferOwnershipTxHash);
566
911
  }
567
912
  // Wait for all actions to be mined
@@ -569,6 +914,9 @@ export const handoverToGovernance = async (extendedClient, deployer, registryAdd
569
914
  await Promise.all(txHashes.map((txHash)=>extendedClient.waitForTransactionReceipt({
570
915
  hash: txHash
571
916
  })));
917
+ return {
918
+ dateGatedRelayerAddress: dateGatedRelayer.address
919
+ };
572
920
  };
573
921
  /*
574
922
  * Adds multiple validators to the rollup
@@ -596,85 +944,94 @@ export const handoverToGovernance = async (extendedClient, deployer, registryAdd
596
944
  }
597
945
  validators = enrichedValidators.filter((v)=>v.status === 0).map((v)=>v.operator);
598
946
  }
599
- if (validators.length > 0) {
600
- const gseContract = new GSEContract(extendedClient, gseAddress);
601
- const multiAdder = await deployer.deploy(MultiAdderArtifact, [
602
- rollupAddress,
603
- deployer.client.account.address
604
- ]);
605
- const makeValidatorTuples = async (validator)=>{
606
- const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
607
- return {
608
- attester: getAddress(validator.attester.toString()),
609
- withdrawer: getAddress(validator.withdrawer.toString()),
610
- ...registrationTuple
611
- };
947
+ if (validators.length === 0) {
948
+ logger.warn('No validators to add. Skipping.');
949
+ return;
950
+ }
951
+ const gseContract = new GSEContract(extendedClient, gseAddress);
952
+ const multiAdder = (await deployer.deploy(MultiAdderArtifact, [
953
+ rollupAddress,
954
+ deployer.client.account.address
955
+ ])).address;
956
+ const makeValidatorTuples = async (validator)=>{
957
+ const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
958
+ return {
959
+ attester: getAddress(validator.attester.toString()),
960
+ withdrawer: getAddress(validator.withdrawer.toString()),
961
+ ...registrationTuple
612
962
  };
613
- const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
614
- // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
615
- const stakeNeeded = activationThreshold * BigInt(validators.length);
963
+ };
964
+ const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
965
+ // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
966
+ const stakeNeeded = activationThreshold * BigInt(validators.length);
967
+ await deployer.l1TxUtils.sendAndMonitorTransaction({
968
+ to: stakingAssetAddress,
969
+ data: encodeFunctionData({
970
+ abi: StakingAssetArtifact.contractAbi,
971
+ functionName: 'mint',
972
+ args: [
973
+ multiAdder.toString(),
974
+ stakeNeeded
975
+ ]
976
+ })
977
+ });
978
+ const entryQueueLengthBefore = await rollup.getEntryQueueLength();
979
+ const validatorCountBefore = await rollup.getActiveAttesterCount();
980
+ logger.info(`Adding ${validators.length} validators to the rollup`);
981
+ const chunkSize = 16;
982
+ // We will add `chunkSize` validators to the queue until we have covered all of our validators.
983
+ // The `chunkSize` needs to be small enough to fit inside a single tx, therefore 16.
984
+ for (const c of chunk(validatorsTuples, chunkSize)){
616
985
  await deployer.l1TxUtils.sendAndMonitorTransaction({
617
- to: stakingAssetAddress,
986
+ to: multiAdder.toString(),
618
987
  data: encodeFunctionData({
619
- abi: StakingAssetArtifact.contractAbi,
620
- functionName: 'mint',
988
+ abi: MultiAdderArtifact.contractAbi,
989
+ functionName: 'addValidators',
621
990
  args: [
622
- multiAdder.toString(),
623
- stakeNeeded
991
+ c,
992
+ BigInt(0)
624
993
  ]
625
994
  })
995
+ }, {
996
+ gasLimit: 16_000_000n
626
997
  });
627
- const entryQueueLengthBefore = await rollup.getEntryQueueLength();
628
- const validatorCountBefore = await rollup.getActiveAttesterCount();
629
- logger.info(`Adding ${validators.length} validators to the rollup`);
630
- // Adding to the queue and flushing need to be done in two transactions
631
- // if we are adding many validators.
632
- if (validatorsTuples.length > 10) {
633
- await deployer.l1TxUtils.sendAndMonitorTransaction({
634
- to: multiAdder.toString(),
635
- data: encodeFunctionData({
636
- abi: MultiAdderArtifact.contractAbi,
637
- functionName: 'addValidators',
638
- args: [
639
- validatorsTuples,
640
- true
641
- ]
642
- })
643
- }, {
644
- gasLimit: 40_000_000n
645
- });
646
- await deployer.l1TxUtils.sendAndMonitorTransaction({
647
- to: rollupAddress,
648
- data: encodeFunctionData({
649
- abi: RollupArtifact.contractAbi,
650
- functionName: 'flushEntryQueue',
651
- args: []
652
- })
653
- }, {
654
- gasLimit: 40_000_000n
655
- });
656
- } else {
657
- await deployer.l1TxUtils.sendAndMonitorTransaction({
658
- to: multiAdder.toString(),
659
- data: encodeFunctionData({
660
- abi: MultiAdderArtifact.contractAbi,
661
- functionName: 'addValidators',
662
- args: [
663
- validatorsTuples,
664
- false
665
- ]
666
- })
667
- }, {
668
- gasLimit: 45_000_000n
669
- });
998
+ }
999
+ // After adding to the queue, we will now try to flush from it.
1000
+ // We are explicitly doing this as a second step instead of as part of adding to benefit
1001
+ // from the accounting used to speed the process up.
1002
+ // As the queue computes the amount of possible flushes in an epoch when told to flush,
1003
+ // waiting until we have added all we want allows us to benefit in the case were we added
1004
+ // enough to pass the bootstrap set size without needing to wait another epoch.
1005
+ // This is useful when we are testing as it speeds up the tests slightly.
1006
+ while(true){
1007
+ // If the queue is empty, we can break
1008
+ if (await rollup.getEntryQueueLength() == 0n) {
1009
+ break;
670
1010
  }
671
- const entryQueueLengthAfter = await rollup.getEntryQueueLength();
672
- const validatorCountAfter = await rollup.getActiveAttesterCount();
673
- if (entryQueueLengthAfter + validatorCountAfter < entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)) {
674
- throw new Error(`Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`);
1011
+ // If there are no available validator flushes, no need to even try
1012
+ if (await rollup.getAvailableValidatorFlushes() == 0n) {
1013
+ break;
675
1014
  }
676
- logger.info(`Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`);
1015
+ // Note that we are flushing at most `chunkSize` at each call
1016
+ await deployer.l1TxUtils.sendAndMonitorTransaction({
1017
+ to: rollup.address,
1018
+ data: encodeFunctionData({
1019
+ abi: RollupArtifact.contractAbi,
1020
+ functionName: 'flushEntryQueue',
1021
+ args: [
1022
+ BigInt(chunkSize)
1023
+ ]
1024
+ })
1025
+ }, {
1026
+ gasLimit: 16_000_000n
1027
+ });
677
1028
  }
1029
+ const entryQueueLengthAfter = await rollup.getEntryQueueLength();
1030
+ const validatorCountAfter = await rollup.getActiveAttesterCount();
1031
+ if (entryQueueLengthAfter + validatorCountAfter < entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)) {
1032
+ throw new Error(`Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}. A likely issue is the bootstrap size.`);
1033
+ }
1034
+ logger.info(`Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`);
678
1035
  }
679
1036
  };
680
1037
  /**
@@ -687,11 +1044,11 @@ export const handoverToGovernance = async (extendedClient, deployer, registryAdd
687
1044
  * @param logger - The logger.
688
1045
  */ // eslint-disable-next-line camelcase
689
1046
  export const cheat_initializeFeeAssetHandler = async (extendedClient, deployer, feeAssetAddress, logger)=>{
690
- const feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
1047
+ const feeAssetHandlerAddress = (await deployer.deploy(FeeAssetHandlerArtifact, [
691
1048
  extendedClient.account.address,
692
1049
  feeAssetAddress.toString(),
693
1050
  BigInt(1e18)
694
- ]);
1051
+ ])).address;
695
1052
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
696
1053
  const { txHash } = await deployer.sendTransaction({
697
1054
  to: feeAssetAddress.toString(),
@@ -720,6 +1077,9 @@ export const cheat_initializeFeeAssetHandler = async (extendedClient, deployer,
720
1077
  */ export const deployL1Contracts = async (rpcUrls, account, chain, logger, args, txUtilsConfig = getL1TxUtilsConfigEnvVars(), createVerificationJson = false)=>{
721
1078
  logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
722
1079
  validateConfig(args);
1080
+ if (args.initialValidators && args.initialValidators.length > 0 && args.existingTokenAddress) {
1081
+ throw new Error('Cannot deploy with both initialValidators and existingTokenAddress. ' + 'Initial validator funding requires minting tokens, which is not possible with an external token.');
1082
+ }
723
1083
  const l1Client = createExtendedL1Client(rpcUrls, account, chain);
724
1084
  // Deploy multicall3 if it does not exist in this network
725
1085
  await deployMulticall3(l1Client, logger);
@@ -757,205 +1117,15 @@ export const cheat_initializeFeeAssetHandler = async (extendedClient, deployer,
757
1117
  logger.verbose('Waiting for rollup and slash factory to be deployed');
758
1118
  await deployer.waitForDeployments();
759
1119
  // Now that the rollup has been deployed and added to the registry, transfer ownership to governance
760
- await handoverToGovernance(l1Client, deployer, registryAddress, gseAddress, coinIssuerAddress, feeAssetAddress, governanceAddress, logger, args.acceleratedTestDeployments);
1120
+ const { dateGatedRelayerAddress } = await handoverToGovernance(l1Client, deployer, registryAddress, gseAddress, coinIssuerAddress, feeAssetAddress, governanceAddress, logger, args.acceleratedTestDeployments, !!args.existingTokenAddress);
761
1121
  logger.info(`Handing over to governance complete`);
762
1122
  logger.verbose(`All transactions for L1 deployment have been mined`);
763
1123
  const l1Contracts = await RegistryContract.collectAddresses(l1Client, registryAddress, 'canonical');
764
1124
  logger.info(`Aztec L1 contracts initialized`, l1Contracts);
765
1125
  // Write verification data (constructor args + linked libraries) to file for later forge verify
766
1126
  if (createVerificationJson) {
767
- try {
768
- // Add Inbox / Outbox verification records (constructor args are created inside RollupCore)
769
- const rollupAddr = l1Contracts.rollupAddress.toString();
770
- const inboxAddr = l1Contracts.inboxAddress.toString();
771
- const outboxAddr = l1Contracts.outboxAddress.toString();
772
- const feeAsset = l1Contracts.feeJuiceAddress.toString();
773
- const version = await rollup.getVersion();
774
- const inboxCtor = encodeAbiParameters([
775
- {
776
- type: 'address'
777
- },
778
- {
779
- type: 'address'
780
- },
781
- {
782
- type: 'uint256'
783
- },
784
- {
785
- type: 'uint256'
786
- }
787
- ], [
788
- rollupAddr,
789
- feeAsset,
790
- version,
791
- BigInt(L1_TO_L2_MSG_SUBTREE_HEIGHT)
792
- ]);
793
- const outboxCtor = encodeAbiParameters([
794
- {
795
- type: 'address'
796
- },
797
- {
798
- type: 'uint256'
799
- }
800
- ], [
801
- rollupAddr,
802
- version
803
- ]);
804
- deployer.verificationRecords.push({
805
- name: 'Inbox',
806
- address: inboxAddr,
807
- constructorArgsHex: inboxCtor,
808
- libraries: []
809
- }, {
810
- name: 'Outbox',
811
- address: outboxAddr,
812
- constructorArgsHex: outboxCtor,
813
- libraries: []
814
- });
815
- // Include Slasher and SlashingProposer (if deployed) in verification data
816
- try {
817
- const slasherAddrHex = await rollup.getSlasher();
818
- const slasherAddr = EthAddress.fromString(slasherAddrHex);
819
- if (!slasherAddr.isZero()) {
820
- // Slasher constructor: (address _vetoer, address _governance)
821
- const slasherCtor = encodeAbiParameters([
822
- {
823
- type: 'address'
824
- },
825
- {
826
- type: 'address'
827
- }
828
- ], [
829
- args.slashingVetoer.toString(),
830
- l1Client.account.address
831
- ]);
832
- deployer.verificationRecords.push({
833
- name: 'Slasher',
834
- address: slasherAddr.toString(),
835
- constructorArgsHex: slasherCtor,
836
- libraries: []
837
- });
838
- // Proposer address is stored in Slasher.PROPOSER()
839
- const proposerAddr = (await rollup.getSlashingProposerAddress()).toString();
840
- // Compute constructor args matching deployment path in RollupCore
841
- const computedRoundSize = BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration);
842
- const computedQuorum = BigInt(args.slashingQuorum ?? args.slashingRoundSizeInEpochs * args.aztecEpochDuration / 2 + 1);
843
- const lifetimeInRounds = BigInt(args.slashingLifetimeInRounds);
844
- const executionDelayInRounds = BigInt(args.slashingExecutionDelayInRounds);
845
- if (args.slasherFlavor === 'tally') {
846
- const slashAmounts = [
847
- args.slashAmountSmall,
848
- args.slashAmountMedium,
849
- args.slashAmountLarge
850
- ];
851
- const committeeSize = BigInt(args.aztecTargetCommitteeSize);
852
- const epochDuration = BigInt(args.aztecEpochDuration);
853
- const slashOffsetInRounds = BigInt(args.slashingOffsetInRounds);
854
- const proposerCtor = encodeAbiParameters([
855
- {
856
- type: 'address'
857
- },
858
- {
859
- type: 'address'
860
- },
861
- {
862
- type: 'uint256'
863
- },
864
- {
865
- type: 'uint256'
866
- },
867
- {
868
- type: 'uint256'
869
- },
870
- {
871
- type: 'uint256'
872
- },
873
- {
874
- type: 'uint256[3]'
875
- },
876
- {
877
- type: 'uint256'
878
- },
879
- {
880
- type: 'uint256'
881
- },
882
- {
883
- type: 'uint256'
884
- }
885
- ], [
886
- rollup.address,
887
- slasherAddr.toString(),
888
- computedQuorum,
889
- computedRoundSize,
890
- lifetimeInRounds,
891
- executionDelayInRounds,
892
- slashAmounts,
893
- committeeSize,
894
- epochDuration,
895
- slashOffsetInRounds
896
- ]);
897
- deployer.verificationRecords.push({
898
- name: 'TallySlashingProposer',
899
- address: proposerAddr,
900
- constructorArgsHex: proposerCtor,
901
- libraries: []
902
- });
903
- } else if (args.slasherFlavor === 'empire') {
904
- const proposerCtor = encodeAbiParameters([
905
- {
906
- type: 'address'
907
- },
908
- {
909
- type: 'address'
910
- },
911
- {
912
- type: 'uint256'
913
- },
914
- {
915
- type: 'uint256'
916
- },
917
- {
918
- type: 'uint256'
919
- },
920
- {
921
- type: 'uint256'
922
- }
923
- ], [
924
- rollup.address,
925
- slasherAddr.toString(),
926
- computedQuorum,
927
- computedRoundSize,
928
- lifetimeInRounds,
929
- executionDelayInRounds
930
- ]);
931
- deployer.verificationRecords.push({
932
- name: 'EmpireSlashingProposer',
933
- address: proposerAddr,
934
- constructorArgsHex: proposerCtor,
935
- libraries: []
936
- });
937
- }
938
- }
939
- } catch (e) {
940
- logger.warn(`Failed to add Slasher/Proposer verification records: ${String(e)}`);
941
- }
942
- const date = new Date();
943
- const formattedDate = date.toISOString().slice(2, 19).replace(/[-T:]/g, '');
944
- // Ensure the verification output directory exists
945
- await mkdir(createVerificationJson, {
946
- recursive: true
947
- });
948
- const verificationOutputPath = `${createVerificationJson}/l1-verify-${chain.id}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
949
- const verificationData = {
950
- chainId: chain.id,
951
- network: networkName,
952
- records: deployer.verificationRecords
953
- };
954
- await writeFile(verificationOutputPath, JSON.stringify(verificationData, null, 2));
955
- logger.info(`Wrote L1 verification data to ${verificationOutputPath}`);
956
- } catch (e) {
957
- logger.warn(`Failed to write L1 verification data file: ${String(e)}`);
958
- }
1127
+ await generateRollupVerificationRecords(rollup, deployer, args, l1Contracts, l1Client, logger);
1128
+ await writeVerificationJson(deployer, createVerificationJson, chain.id, '', logger);
959
1129
  }
960
1130
  if (isAnvilTestChain(chain.id)) {
961
1131
  // @note We make a time jump PAST the very first slot to not have to deal with the edge case of the first slot.
@@ -990,7 +1160,8 @@ export const cheat_initializeFeeAssetHandler = async (extendedClient, deployer,
990
1160
  feeAssetHandlerAddress,
991
1161
  stakingAssetHandlerAddress,
992
1162
  zkPassportVerifierAddress,
993
- coinIssuerAddress
1163
+ coinIssuerAddress,
1164
+ dateGatedRelayerAddress
994
1165
  }
995
1166
  };
996
1167
  };
@@ -1015,14 +1186,20 @@ export class L1Deployer {
1015
1186
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), {
1016
1187
  size: 32
1017
1188
  }) : undefined;
1018
- this.l1TxUtils = createL1TxUtilsFromViemWallet(this.client, this.logger, dateProvider, this.txUtilsConfig, this.acceleratedTestDeployments);
1189
+ this.l1TxUtils = createL1TxUtilsFromViemWallet(this.client, {
1190
+ logger: this.logger,
1191
+ dateProvider
1192
+ }, {
1193
+ ...this.txUtilsConfig,
1194
+ debugMaxGasLimit: acceleratedTestDeployments
1195
+ });
1019
1196
  }
1020
1197
  async deploy(params, args, opts = {}) {
1021
1198
  this.logger.debug(`Deploying ${params.name} contract`, {
1022
1199
  args
1023
1200
  });
1024
1201
  try {
1025
- const { txHash, address, deployedLibraries } = await deployL1Contract(this.client, params.contractAbi, params.contractBytecode, args ?? [], {
1202
+ const { txHash, address, deployedLibraries, existed } = await deployL1Contract(this.client, params.contractAbi, params.contractBytecode, args ?? [], {
1026
1203
  salt: this.salt,
1027
1204
  libraries: params.libraries,
1028
1205
  logger: this.logger,
@@ -1053,7 +1230,10 @@ export class L1Deployer {
1053
1230
  libraries: deployedLibraries ?? []
1054
1231
  });
1055
1232
  }
1056
- return address;
1233
+ return {
1234
+ address,
1235
+ existed
1236
+ };
1057
1237
  } catch (error) {
1058
1238
  throw new Error(`Failed to deploy ${params.name}`, {
1059
1239
  cause: formatViemError(error)
@@ -1083,10 +1263,13 @@ export class L1Deployer {
1083
1263
  });
1084
1264
  }
1085
1265
  sendTransaction(tx, options) {
1086
- return this.l1TxUtils.sendTransaction(tx, options);
1266
+ return this.l1TxUtils.sendTransaction(tx, options).then(({ txHash, state })=>({
1267
+ txHash,
1268
+ gasLimit: state.gasLimit,
1269
+ gasPrice: state.gasPrice
1270
+ }));
1087
1271
  }
1088
1272
  }
1089
- // docs:start:deployL1Contract
1090
1273
  /**
1091
1274
  * Helper function to deploy ETH contracts.
1092
1275
  * @param walletClient - A viem WalletClient.
@@ -1104,7 +1287,12 @@ export class L1Deployer {
1104
1287
  let { l1TxUtils } = opts;
1105
1288
  if (!l1TxUtils) {
1106
1289
  const config = getL1TxUtilsConfigEnvVars();
1107
- l1TxUtils = createL1TxUtilsFromViemWallet(extendedClient, logger, undefined, config, acceleratedTestDeployments);
1290
+ l1TxUtils = createL1TxUtilsFromViemWallet(extendedClient, {
1291
+ logger
1292
+ }, {
1293
+ ...config,
1294
+ debugMaxGasLimit: acceleratedTestDeployments
1295
+ });
1108
1296
  }
1109
1297
  if (libraries) {
1110
1298
  // Note that this does NOT work well for linked libraries having linked libraries.
@@ -1179,6 +1367,7 @@ export class L1Deployer {
1179
1367
  logger?.verbose(`Skipping waiting for linked libraries to be deployed ${acceleratedTestDeployments ? '(accelerated test deployments)' : ''}`);
1180
1368
  }
1181
1369
  }
1370
+ let existed = false;
1182
1371
  if (saltFromOpts) {
1183
1372
  logger?.info(`Deploying contract with salt ${saltFromOpts}`);
1184
1373
  const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, saltFromOpts);
@@ -1193,9 +1382,8 @@ export class L1Deployer {
1193
1382
  data: concatHex([
1194
1383
  salt,
1195
1384
  calldata
1196
- ])
1197
- }, {
1198
- gasLimit
1385
+ ]),
1386
+ gas: gasLimit
1199
1387
  });
1200
1388
  } catch (err) {
1201
1389
  logger?.error(`Failed to simulate deployment tx using universal deployer`, err);
@@ -1205,7 +1393,8 @@ export class L1Deployer {
1205
1393
  abi,
1206
1394
  bytecode,
1207
1395
  args
1208
- })
1396
+ }),
1397
+ gas: gasLimit
1209
1398
  });
1210
1399
  }
1211
1400
  const res = await l1TxUtils.sendTransaction({
@@ -1221,6 +1410,7 @@ export class L1Deployer {
1221
1410
  logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
1222
1411
  } else {
1223
1412
  logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
1413
+ existed = true;
1224
1414
  }
1225
1415
  } else {
1226
1416
  const deployData = encodeDeployData({
@@ -1231,6 +1421,8 @@ export class L1Deployer {
1231
1421
  const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
1232
1422
  to: null,
1233
1423
  data: deployData
1424
+ }, {
1425
+ gasLimit
1234
1426
  });
1235
1427
  txHash = receipt.transactionHash;
1236
1428
  resultingAddress = receipt.contractAddress;
@@ -1241,7 +1433,8 @@ export class L1Deployer {
1241
1433
  return {
1242
1434
  address: EthAddress.fromString(resultingAddress),
1243
1435
  txHash,
1244
- deployedLibraries
1436
+ deployedLibraries,
1437
+ existed
1245
1438
  };
1246
1439
  }
1247
1440
  export function getExpectedAddress(abi, bytecode, args, salt) {
@@ -1264,4 +1457,4 @@ export function getExpectedAddress(abi, bytecode, args, salt) {
1264
1457
  paddedSalt,
1265
1458
  calldata
1266
1459
  };
1267
- } // docs:end:deployL1Contract
1460
+ }