@aztec/ethereum 0.0.0-test.0

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 (118) hide show
  1. package/README.md +3 -0
  2. package/dest/chain.d.ts +25 -0
  3. package/dest/chain.d.ts.map +1 -0
  4. package/dest/chain.js +53 -0
  5. package/dest/client.d.ts +16 -0
  6. package/dest/client.d.ts.map +1 -0
  7. package/dest/client.js +31 -0
  8. package/dest/config.d.ts +39 -0
  9. package/dest/config.d.ts.map +1 -0
  10. package/dest/config.js +70 -0
  11. package/dest/constants.d.ts +4 -0
  12. package/dest/constants.d.ts.map +1 -0
  13. package/dest/constants.js +2 -0
  14. package/dest/contracts/empire_base.d.ts +13 -0
  15. package/dest/contracts/empire_base.d.ts.map +1 -0
  16. package/dest/contracts/empire_base.js +11 -0
  17. package/dest/contracts/fee_juice.d.ts +15 -0
  18. package/dest/contracts/fee_juice.d.ts.map +1 -0
  19. package/dest/contracts/fee_juice.js +52 -0
  20. package/dest/contracts/forwarder.d.ts +24 -0
  21. package/dest/contracts/forwarder.d.ts.map +1 -0
  22. package/dest/contracts/forwarder.js +101 -0
  23. package/dest/contracts/governance.d.ts +79 -0
  24. package/dest/contracts/governance.d.ts.map +1 -0
  25. package/dest/contracts/governance.js +247 -0
  26. package/dest/contracts/governance_proposer.d.ts +28 -0
  27. package/dest/contracts/governance_proposer.d.ts.map +1 -0
  28. package/dest/contracts/governance_proposer.js +82 -0
  29. package/dest/contracts/index.d.ts +9 -0
  30. package/dest/contracts/index.d.ts.map +1 -0
  31. package/dest/contracts/index.js +8 -0
  32. package/dest/contracts/registry.d.ts +24 -0
  33. package/dest/contracts/registry.d.ts.map +1 -0
  34. package/dest/contracts/registry.js +85 -0
  35. package/dest/contracts/rollup.d.ts +92 -0
  36. package/dest/contracts/rollup.d.ts.map +1 -0
  37. package/dest/contracts/rollup.js +234 -0
  38. package/dest/contracts/slashing_proposer.d.ts +21 -0
  39. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  40. package/dest/contracts/slashing_proposer.js +47 -0
  41. package/dest/deploy_l1_contracts.d.ts +21210 -0
  42. package/dest/deploy_l1_contracts.d.ts.map +1 -0
  43. package/dest/deploy_l1_contracts.js +687 -0
  44. package/dest/eth_cheat_codes.d.ts +147 -0
  45. package/dest/eth_cheat_codes.d.ts.map +1 -0
  46. package/dest/eth_cheat_codes.js +303 -0
  47. package/dest/index.d.ts +14 -0
  48. package/dest/index.d.ts.map +1 -0
  49. package/dest/index.js +13 -0
  50. package/dest/l1_contract_addresses.d.ts +57 -0
  51. package/dest/l1_contract_addresses.d.ts.map +1 -0
  52. package/dest/l1_contract_addresses.js +97 -0
  53. package/dest/l1_reader.d.ts +16 -0
  54. package/dest/l1_reader.d.ts.map +1 -0
  55. package/dest/l1_reader.js +27 -0
  56. package/dest/l1_tx_utils.d.ts +192 -0
  57. package/dest/l1_tx_utils.d.ts.map +1 -0
  58. package/dest/l1_tx_utils.js +641 -0
  59. package/dest/l1_tx_utils_with_blobs.d.ts +12 -0
  60. package/dest/l1_tx_utils_with_blobs.d.ts.map +1 -0
  61. package/dest/l1_tx_utils_with_blobs.js +64 -0
  62. package/dest/queries.d.ts +12 -0
  63. package/dest/queries.d.ts.map +1 -0
  64. package/dest/queries.js +35 -0
  65. package/dest/test/delayed_tx_utils.d.ts +8 -0
  66. package/dest/test/delayed_tx_utils.d.ts.map +1 -0
  67. package/dest/test/delayed_tx_utils.js +21 -0
  68. package/dest/test/eth_cheat_codes_with_state.d.ts +18 -0
  69. package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -0
  70. package/dest/test/eth_cheat_codes_with_state.js +34 -0
  71. package/dest/test/index.d.ts +6 -0
  72. package/dest/test/index.d.ts.map +1 -0
  73. package/dest/test/index.js +5 -0
  74. package/dest/test/start_anvil.d.ts +12 -0
  75. package/dest/test/start_anvil.d.ts.map +1 -0
  76. package/dest/test/start_anvil.js +46 -0
  77. package/dest/test/tx_delayer.d.ts +25 -0
  78. package/dest/test/tx_delayer.d.ts.map +1 -0
  79. package/dest/test/tx_delayer.js +116 -0
  80. package/dest/test/upgrade_utils.d.ts +11 -0
  81. package/dest/test/upgrade_utils.d.ts.map +1 -0
  82. package/dest/test/upgrade_utils.js +104 -0
  83. package/dest/types.d.ts +14 -0
  84. package/dest/types.d.ts.map +1 -0
  85. package/dest/types.js +1 -0
  86. package/dest/utils.d.ts +24 -0
  87. package/dest/utils.d.ts.map +1 -0
  88. package/dest/utils.js +209 -0
  89. package/package.json +98 -0
  90. package/src/chain.ts +71 -0
  91. package/src/client.ts +58 -0
  92. package/src/config.ts +103 -0
  93. package/src/constants.ts +4 -0
  94. package/src/contracts/empire_base.ts +19 -0
  95. package/src/contracts/fee_juice.ts +43 -0
  96. package/src/contracts/forwarder.ts +132 -0
  97. package/src/contracts/governance.ts +285 -0
  98. package/src/contracts/governance_proposer.ts +82 -0
  99. package/src/contracts/index.ts +8 -0
  100. package/src/contracts/registry.ts +106 -0
  101. package/src/contracts/rollup.ts +274 -0
  102. package/src/contracts/slashing_proposer.ts +51 -0
  103. package/src/deploy_l1_contracts.ts +948 -0
  104. package/src/eth_cheat_codes.ts +314 -0
  105. package/src/index.ts +13 -0
  106. package/src/l1_contract_addresses.ts +109 -0
  107. package/src/l1_reader.ts +42 -0
  108. package/src/l1_tx_utils.ts +847 -0
  109. package/src/l1_tx_utils_with_blobs.ts +86 -0
  110. package/src/queries.ts +58 -0
  111. package/src/test/delayed_tx_utils.ts +24 -0
  112. package/src/test/eth_cheat_codes_with_state.ts +38 -0
  113. package/src/test/index.ts +5 -0
  114. package/src/test/start_anvil.ts +52 -0
  115. package/src/test/tx_delayer.ts +163 -0
  116. package/src/test/upgrade_utils.ts +100 -0
  117. package/src/types.ts +33 -0
  118. package/src/utils.ts +276 -0
@@ -0,0 +1,687 @@
1
+ import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import { createLogger } from '@aztec/foundation/log';
3
+ import { CoinIssuerAbi, CoinIssuerBytecode, ExtRollupLibAbi, ExtRollupLibBytecode, FeeJuicePortalAbi, FeeJuicePortalBytecode, ForwarderAbi, ForwarderBytecode, GovernanceAbi, GovernanceBytecode, GovernanceProposerAbi, GovernanceProposerBytecode, InboxAbi, InboxBytecode, OutboxAbi, OutboxBytecode, RegisterNewRollupVersionPayloadAbi, RegisterNewRollupVersionPayloadBytecode, RegistryAbi, RegistryBytecode, RewardDistributorAbi, RewardDistributorBytecode, RollupAbi, RollupBytecode, RollupLinkReferences, SlashFactoryAbi, SlashFactoryBytecode, TestERC20Abi, TestERC20Bytecode, ValidatorSelectionLibAbi, ValidatorSelectionLibBytecode } from '@aztec/l1-artifacts';
4
+ import { concatHex, createPublicClient, createWalletClient, encodeDeployData, encodeFunctionData, fallback, getAddress, getContract, getContractAddress, http, numberToHex, padHex, publicActions } from 'viem';
5
+ import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
6
+ import { foundry } from 'viem/chains';
7
+ import { isAnvilTestChain } from './chain.js';
8
+ import { RegistryContract } from './contracts/registry.js';
9
+ import { RollupContract } from './contracts/rollup.js';
10
+ import { L1TxUtils, defaultL1TxUtilsConfig } from './l1_tx_utils.js';
11
+ export const DEPLOYER_ADDRESS = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
12
+ export const l1Artifacts = {
13
+ registry: {
14
+ contractAbi: RegistryAbi,
15
+ contractBytecode: RegistryBytecode
16
+ },
17
+ inbox: {
18
+ contractAbi: InboxAbi,
19
+ contractBytecode: InboxBytecode
20
+ },
21
+ outbox: {
22
+ contractAbi: OutboxAbi,
23
+ contractBytecode: OutboxBytecode
24
+ },
25
+ rollup: {
26
+ contractAbi: RollupAbi,
27
+ contractBytecode: RollupBytecode,
28
+ libraries: {
29
+ linkReferences: RollupLinkReferences,
30
+ libraryCode: {
31
+ ValidatorSelectionLib: {
32
+ contractAbi: ValidatorSelectionLibAbi,
33
+ contractBytecode: ValidatorSelectionLibBytecode
34
+ },
35
+ ExtRollupLib: {
36
+ contractAbi: ExtRollupLibAbi,
37
+ contractBytecode: ExtRollupLibBytecode
38
+ }
39
+ }
40
+ }
41
+ },
42
+ stakingAsset: {
43
+ contractAbi: TestERC20Abi,
44
+ contractBytecode: TestERC20Bytecode
45
+ },
46
+ feeAsset: {
47
+ contractAbi: TestERC20Abi,
48
+ contractBytecode: TestERC20Bytecode
49
+ },
50
+ feeJuicePortal: {
51
+ contractAbi: FeeJuicePortalAbi,
52
+ contractBytecode: FeeJuicePortalBytecode
53
+ },
54
+ rewardDistributor: {
55
+ contractAbi: RewardDistributorAbi,
56
+ contractBytecode: RewardDistributorBytecode
57
+ },
58
+ coinIssuer: {
59
+ contractAbi: CoinIssuerAbi,
60
+ contractBytecode: CoinIssuerBytecode
61
+ },
62
+ governanceProposer: {
63
+ contractAbi: GovernanceProposerAbi,
64
+ contractBytecode: GovernanceProposerBytecode
65
+ },
66
+ governance: {
67
+ contractAbi: GovernanceAbi,
68
+ contractBytecode: GovernanceBytecode
69
+ },
70
+ slashFactory: {
71
+ contractAbi: SlashFactoryAbi,
72
+ contractBytecode: SlashFactoryBytecode
73
+ },
74
+ registerNewRollupVersionPayload: {
75
+ contractAbi: RegisterNewRollupVersionPayloadAbi,
76
+ contractBytecode: RegisterNewRollupVersionPayloadBytecode
77
+ }
78
+ };
79
+ /**
80
+ * Creates a wallet and a public viem client for interacting with L1.
81
+ * @param rpcUrls - List of RPC URLs to connect to L1.
82
+ * @param mnemonicOrPrivateKeyOrHdAccount - Mnemonic or account for the wallet client.
83
+ * @param chain - Optional chain spec (defaults to local foundry).
84
+ * @param addressIndex - Optional index of the address to use from the mnemonic.
85
+ * @returns - A wallet and a public client.
86
+ */ export function createL1Clients(rpcUrls, mnemonicOrPrivateKeyOrHdAccount, chain = foundry, addressIndex) {
87
+ const hdAccount = typeof mnemonicOrPrivateKeyOrHdAccount === 'string' ? mnemonicOrPrivateKeyOrHdAccount.startsWith('0x') ? privateKeyToAccount(mnemonicOrPrivateKeyOrHdAccount) : mnemonicToAccount(mnemonicOrPrivateKeyOrHdAccount, {
88
+ addressIndex
89
+ }) : mnemonicOrPrivateKeyOrHdAccount;
90
+ // From what I can see, this is the difference between the HDAccount and the PrivateKeyAccount
91
+ // and we don't need it for anything. This lets us use the same type for both.
92
+ // eslint-disable-next-line camelcase
93
+ hdAccount.experimental_signAuthorization ??= ()=>{
94
+ throw new Error('experimental_signAuthorization not implemented for HDAccount');
95
+ };
96
+ const walletClient = createWalletClient({
97
+ account: hdAccount,
98
+ chain,
99
+ transport: fallback(rpcUrls.map((url)=>http(url)))
100
+ }).extend(publicActions);
101
+ const publicClient = createPublicClient({
102
+ chain,
103
+ transport: fallback(rpcUrls.map((url)=>http(url))),
104
+ pollingInterval: 100
105
+ });
106
+ return {
107
+ walletClient,
108
+ publicClient
109
+ };
110
+ }
111
+ export const deployRollupAndPeriphery = async (clients, args, registryAddress, logger, txUtilsConfig)=>{
112
+ const deployer = new L1Deployer(clients.walletClient, clients.publicClient, args.salt, args.acceleratedTestDeployments, logger, txUtilsConfig);
113
+ const addresses = await RegistryContract.collectAddresses(clients.publicClient, registryAddress, 'canonical');
114
+ const rollup = await deployRollup(clients, deployer, args, addresses, logger);
115
+ const payloadAddress = await deployUpgradePayload(deployer, {
116
+ registryAddress: addresses.registryAddress,
117
+ rollupAddress: EthAddress.fromString(rollup.address)
118
+ });
119
+ const slashFactoryAddress = await deploySlashFactory(deployer, rollup.address, logger);
120
+ await deployer.waitForDeployments();
121
+ return {
122
+ rollup,
123
+ payloadAddress,
124
+ slashFactoryAddress
125
+ };
126
+ };
127
+ export const deploySlashFactory = async (deployer, rollupAddress, logger)=>{
128
+ const slashFactoryAddress = await deployer.deploy(l1Artifacts.slashFactory, [
129
+ rollupAddress
130
+ ]);
131
+ logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
132
+ return slashFactoryAddress;
133
+ };
134
+ export const deployUpgradePayload = async (deployer, addresses)=>{
135
+ const payloadAddress = await deployer.deploy(l1Artifacts.registerNewRollupVersionPayload, [
136
+ addresses.registryAddress.toString(),
137
+ addresses.rollupAddress.toString()
138
+ ]);
139
+ return payloadAddress;
140
+ };
141
+ export const deployRollup = async (clients, deployer, args, addresses, logger)=>{
142
+ const rollupConfigArgs = {
143
+ aztecSlotDuration: args.aztecSlotDuration,
144
+ aztecEpochDuration: args.aztecEpochDuration,
145
+ targetCommitteeSize: args.aztecTargetCommitteeSize,
146
+ aztecProofSubmissionWindow: args.aztecProofSubmissionWindow,
147
+ minimumStake: args.minimumStake,
148
+ slashingQuorum: args.slashingQuorum,
149
+ slashingRoundSize: args.slashingRoundSize
150
+ };
151
+ const genesisStateArgs = {
152
+ vkTreeRoot: args.vkTreeRoot.toString(),
153
+ protocolContractTreeRoot: args.protocolContractTreeRoot.toString(),
154
+ genesisArchiveRoot: args.genesisArchiveRoot.toString(),
155
+ genesisBlockHash: args.genesisBlockHash.toString()
156
+ };
157
+ logger.verbose(`Rollup config args`, rollupConfigArgs);
158
+ const rollupArgs = [
159
+ addresses.feeJuicePortalAddress.toString(),
160
+ addresses.rewardDistributorAddress.toString(),
161
+ addresses.stakingAssetAddress.toString(),
162
+ clients.walletClient.account.address.toString(),
163
+ genesisStateArgs,
164
+ rollupConfigArgs
165
+ ];
166
+ const rollupAddress = await deployer.deploy(l1Artifacts.rollup, rollupArgs);
167
+ logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
168
+ await deployer.waitForDeployments();
169
+ logger.verbose(`All core contracts have been deployed`);
170
+ const rollup = getContract({
171
+ address: getAddress(rollupAddress.toString()),
172
+ abi: l1Artifacts.rollup.contractAbi,
173
+ client: clients.walletClient
174
+ });
175
+ const txHashes = [];
176
+ if (args.initialValidators && args.initialValidators.length > 0) {
177
+ // Check if some of the initial validators are already registered, so we support idempotent deployments
178
+ let newValidatorsAddresses = args.initialValidators.map((v)=>v.toString());
179
+ if (!args.acceleratedTestDeployments) {
180
+ const validatorsInfo = await Promise.all(args.initialValidators.map(async (address)=>({
181
+ address,
182
+ ...await rollup.read.getInfo([
183
+ address.toString()
184
+ ])
185
+ })));
186
+ const existingValidators = validatorsInfo.filter((v)=>v.status !== 0);
187
+ if (existingValidators.length > 0) {
188
+ logger.warn(`Validators ${existingValidators.map((v)=>v.address).join(', ')} already exist. Skipping from initialization.`);
189
+ }
190
+ newValidatorsAddresses = validatorsInfo.filter((v)=>v.status === 0).map((v)=>v.address.toString());
191
+ }
192
+ if (newValidatorsAddresses.length > 0) {
193
+ // Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch.
194
+ const stakeNeeded = args.minimumStake * BigInt(newValidatorsAddresses.length);
195
+ await Promise.all([
196
+ await deployer.sendTransaction({
197
+ to: addresses.stakingAssetAddress.toString(),
198
+ data: encodeFunctionData({
199
+ abi: l1Artifacts.stakingAsset.contractAbi,
200
+ functionName: 'mint',
201
+ args: [
202
+ clients.walletClient.account.address,
203
+ stakeNeeded
204
+ ]
205
+ })
206
+ }),
207
+ await deployer.sendTransaction({
208
+ to: addresses.stakingAssetAddress.toString(),
209
+ data: encodeFunctionData({
210
+ abi: l1Artifacts.stakingAsset.contractAbi,
211
+ functionName: 'approve',
212
+ args: [
213
+ rollupAddress.toString(),
214
+ stakeNeeded
215
+ ]
216
+ })
217
+ })
218
+ ].map((tx)=>clients.publicClient.waitForTransactionReceipt({
219
+ hash: tx.txHash
220
+ })));
221
+ const validators = newValidatorsAddresses.map((v)=>({
222
+ attester: v,
223
+ proposer: getExpectedAddress(ForwarderAbi, ForwarderBytecode, [
224
+ v
225
+ ], v).address,
226
+ withdrawer: v,
227
+ amount: args.minimumStake
228
+ }));
229
+ // const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([validators]);
230
+ const initiateValidatorSetTxHash = await deployer.walletClient.writeContract({
231
+ address: rollupAddress.toString(),
232
+ abi: l1Artifacts.rollup.contractAbi,
233
+ functionName: 'cheat__InitialiseValidatorSet',
234
+ args: [
235
+ validators
236
+ ]
237
+ });
238
+ txHashes.push(initiateValidatorSetTxHash);
239
+ logger.info(`Initialized validator set`, {
240
+ validators,
241
+ txHash: initiateValidatorSetTxHash
242
+ });
243
+ }
244
+ }
245
+ await Promise.all(txHashes.map((txHash)=>clients.publicClient.waitForTransactionReceipt({
246
+ hash: txHash
247
+ })));
248
+ return new RollupContract(clients.publicClient, rollupAddress);
249
+ };
250
+ /**
251
+ * Deploys the aztec L1 contracts; Rollup & (optionally) Decoder Helper.
252
+ * @param rpcUrls - List of URLs of the ETH RPC to use for deployment.
253
+ * @param account - Private Key or HD Account that will deploy the contracts.
254
+ * @param chain - The chain instance to deploy to.
255
+ * @param logger - A logger object.
256
+ * @param args - Arguments for initialization of L1 contracts
257
+ * @returns A list of ETH addresses of the deployed contracts.
258
+ */ export const deployL1Contracts = async (rpcUrls, account, chain, logger, args, txUtilsConfig = defaultL1TxUtilsConfig)=>{
259
+ const clients = createL1Clients(rpcUrls, account, chain);
260
+ const { walletClient, publicClient } = clients;
261
+ // We are assuming that you are running this on a local anvil node which have 1s block times
262
+ // To align better with actual deployment, we update the block interval to 12s
263
+ const rpcCall = async (method, params)=>{
264
+ logger.info(`Calling ${method} with params: ${JSON.stringify(params)}`);
265
+ return await publicClient.transport.request({
266
+ method,
267
+ params
268
+ });
269
+ };
270
+ if (isAnvilTestChain(chain.id)) {
271
+ try {
272
+ await rpcCall('anvil_setBlockTimestampInterval', [
273
+ args.ethereumSlotDuration
274
+ ]);
275
+ logger.warn(`Set block interval to ${args.ethereumSlotDuration}`);
276
+ } catch (e) {
277
+ logger.error(`Error setting block interval: ${e}`);
278
+ }
279
+ }
280
+ logger.verbose(`Deploying contracts from ${account.address.toString()}`);
281
+ // Governance stuff
282
+ const deployer = new L1Deployer(walletClient, publicClient, args.salt, args.acceleratedTestDeployments, logger, txUtilsConfig);
283
+ const registryAddress = await deployer.deploy(l1Artifacts.registry, [
284
+ account.address.toString()
285
+ ]);
286
+ logger.verbose(`Deployed Registry at ${registryAddress}`);
287
+ const feeAssetAddress = await deployer.deploy(l1Artifacts.feeAsset, [
288
+ 'FeeJuice',
289
+ 'FEE',
290
+ account.address.toString()
291
+ ]);
292
+ logger.verbose(`Deployed Fee Juice at ${feeAssetAddress}`);
293
+ const stakingAssetAddress = await deployer.deploy(l1Artifacts.stakingAsset, [
294
+ 'Staking',
295
+ 'STK',
296
+ account.address.toString()
297
+ ]);
298
+ logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
299
+ const governanceProposerAddress = await deployer.deploy(l1Artifacts.governanceProposer, [
300
+ registryAddress.toString(),
301
+ args.governanceProposerQuorum,
302
+ args.governanceProposerRoundSize
303
+ ]);
304
+ logger.verbose(`Deployed GovernanceProposer at ${governanceProposerAddress}`);
305
+ // @note @LHerskind the assets are expected to be the same at some point, but for better
306
+ // configurability they are different for now.
307
+ const governanceAddress = await deployer.deploy(l1Artifacts.governance, [
308
+ feeAssetAddress.toString(),
309
+ governanceProposerAddress.toString()
310
+ ]);
311
+ logger.verbose(`Deployed Governance at ${governanceAddress}`);
312
+ const coinIssuerAddress = await deployer.deploy(l1Artifacts.coinIssuer, [
313
+ feeAssetAddress.toString(),
314
+ 1n * 10n ** 18n,
315
+ governanceAddress.toString()
316
+ ]);
317
+ logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
318
+ const rewardDistributorAddress = await deployer.deploy(l1Artifacts.rewardDistributor, [
319
+ feeAssetAddress.toString(),
320
+ registryAddress.toString(),
321
+ governanceAddress.toString()
322
+ ]);
323
+ logger.verbose(`Deployed RewardDistributor at ${rewardDistributorAddress}`);
324
+ const feeJuicePortalAddress = await deployer.deploy(l1Artifacts.feeJuicePortal, [
325
+ registryAddress.toString(),
326
+ feeAssetAddress.toString(),
327
+ args.l2FeeJuiceAddress.toString()
328
+ ]);
329
+ logger.verbose(`Deployed Fee Juice Portal at ${feeJuicePortalAddress}`);
330
+ logger.verbose(`Waiting for governance contracts to be deployed`);
331
+ await deployer.waitForDeployments();
332
+ logger.verbose(`All governance contracts deployed`);
333
+ const feeJuicePortal = getContract({
334
+ address: feeJuicePortalAddress.toString(),
335
+ abi: l1Artifacts.feeJuicePortal.contractAbi,
336
+ client: walletClient
337
+ });
338
+ const feeAsset = getContract({
339
+ address: feeAssetAddress.toString(),
340
+ abi: l1Artifacts.feeAsset.contractAbi,
341
+ client: walletClient
342
+ });
343
+ // Transaction hashes to await
344
+ const txHashes = [];
345
+ if (args.acceleratedTestDeployments || !await feeAsset.read.freeForAll()) {
346
+ const { txHash } = await deployer.sendTransaction({
347
+ to: feeAssetAddress.toString(),
348
+ data: encodeFunctionData({
349
+ abi: l1Artifacts.feeAsset.contractAbi,
350
+ functionName: 'setFreeForAll',
351
+ args: [
352
+ true
353
+ ]
354
+ })
355
+ });
356
+ logger.verbose(`Fee asset set to free for all in ${txHash}`);
357
+ txHashes.push(txHash);
358
+ }
359
+ if (args.acceleratedTestDeployments || await feeAsset.read.owner() !== getAddress(coinIssuerAddress.toString())) {
360
+ const { txHash } = await deployer.sendTransaction({
361
+ to: feeAssetAddress.toString(),
362
+ data: encodeFunctionData({
363
+ abi: l1Artifacts.feeAsset.contractAbi,
364
+ functionName: 'transferOwnership',
365
+ args: [
366
+ coinIssuerAddress.toString()
367
+ ]
368
+ })
369
+ });
370
+ logger.verbose(`Fee asset transferred ownership to coin issuer in ${txHash}`);
371
+ txHashes.push(txHash);
372
+ }
373
+ // @note This value MUST match what is in `constants.nr`. It is currently specified here instead of just importing
374
+ // because there is circular dependency hell. This is a temporary solution. #3342
375
+ // @todo #8084
376
+ // fund the portal contract with Fee Juice
377
+ const FEE_JUICE_INITIAL_MINT = 200000000000000000000000n;
378
+ // In fast mode, use the L1TxUtils to send transactions with nonce management
379
+ const { txHash: mintTxHash } = await deployer.sendTransaction({
380
+ to: feeAssetAddress.toString(),
381
+ data: encodeFunctionData({
382
+ abi: l1Artifacts.feeAsset.contractAbi,
383
+ functionName: 'mint',
384
+ args: [
385
+ feeJuicePortalAddress.toString(),
386
+ FEE_JUICE_INITIAL_MINT
387
+ ]
388
+ })
389
+ });
390
+ logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash} (accelerated test deployments)`);
391
+ txHashes.push(mintTxHash);
392
+ // @note This is used to ensure we fully wait for the transaction when running against a real chain
393
+ // otherwise we execute subsequent transactions too soon
394
+ if (!args.acceleratedTestDeployments) {
395
+ await publicClient.waitForTransactionReceipt({
396
+ hash: mintTxHash
397
+ });
398
+ logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash}`);
399
+ }
400
+ // Check if portal needs initialization
401
+ let needsInitialization = args.acceleratedTestDeployments;
402
+ if (!args.acceleratedTestDeployments) {
403
+ // Only check if not in fast mode and not already known to need initialization
404
+ needsInitialization = !await feeJuicePortal.read.initialized();
405
+ }
406
+ if (needsInitialization) {
407
+ const { txHash: initPortalTxHash } = await deployer.sendTransaction({
408
+ to: feeJuicePortalAddress.toString(),
409
+ data: encodeFunctionData({
410
+ abi: l1Artifacts.feeJuicePortal.contractAbi,
411
+ functionName: 'initialize',
412
+ args: []
413
+ })
414
+ });
415
+ txHashes.push(initPortalTxHash);
416
+ logger.verbose(`Fee juice portal initializing in tx ${initPortalTxHash}`);
417
+ } else {
418
+ logger.verbose(`Fee juice portal is already initialized`);
419
+ }
420
+ logger.verbose(`Initialized Fee Juice Portal at ${feeJuicePortalAddress} to bridge between L1 ${feeAssetAddress} to L2 ${args.l2FeeJuiceAddress}`);
421
+ const rollup = await deployRollup({
422
+ walletClient,
423
+ publicClient
424
+ }, deployer, args, {
425
+ feeJuicePortalAddress,
426
+ rewardDistributorAddress,
427
+ stakingAssetAddress
428
+ }, logger);
429
+ const slashFactoryAddress = await deploySlashFactory(deployer, rollup.address, logger);
430
+ logger.verbose('Waiting for rollup and slash factory to be deployed');
431
+ await deployer.waitForDeployments();
432
+ logger.verbose(`Rollup and slash factory deployed`);
433
+ // We need to call a function on the registry to set the various contract addresses.
434
+ const registryContract = getContract({
435
+ address: getAddress(registryAddress.toString()),
436
+ abi: l1Artifacts.registry.contractAbi,
437
+ client: walletClient
438
+ });
439
+ if (args.acceleratedTestDeployments || !await registryContract.read.isRollupRegistered([
440
+ getAddress(rollup.address.toString())
441
+ ])) {
442
+ const { txHash: upgradeTxHash } = await deployer.sendTransaction({
443
+ to: registryAddress.toString(),
444
+ data: encodeFunctionData({
445
+ abi: l1Artifacts.registry.contractAbi,
446
+ functionName: 'upgrade',
447
+ args: [
448
+ getAddress(rollup.address.toString())
449
+ ]
450
+ })
451
+ });
452
+ logger.verbose(`Upgrading registry contract at ${registryAddress} to rollup ${rollup.address} in tx ${upgradeTxHash}`);
453
+ txHashes.push(upgradeTxHash);
454
+ } else {
455
+ logger.verbose(`Registry ${registryAddress} has already registered rollup ${rollup.address}`);
456
+ }
457
+ // If the owner is not the Governance contract, transfer ownership to the Governance contract
458
+ if (args.acceleratedTestDeployments || await registryContract.read.owner() !== getAddress(governanceAddress.toString())) {
459
+ // TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
460
+ const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
461
+ to: registryAddress.toString(),
462
+ data: encodeFunctionData({
463
+ abi: l1Artifacts.registry.contractAbi,
464
+ functionName: 'transferOwnership',
465
+ args: [
466
+ getAddress(governanceAddress.toString())
467
+ ]
468
+ })
469
+ });
470
+ logger.verbose(`Transferring the ownership of the registry contract at ${registryAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`);
471
+ txHashes.push(transferOwnershipTxHash);
472
+ }
473
+ // Wait for all actions to be mined
474
+ await Promise.all(txHashes.map((txHash)=>publicClient.waitForTransactionReceipt({
475
+ hash: txHash
476
+ })));
477
+ logger.verbose(`All transactions for L1 deployment have been mined`);
478
+ const l1Contracts = await RegistryContract.collectAddresses(publicClient, registryAddress, 'canonical');
479
+ logger.info(`Aztec L1 contracts initialized`, l1Contracts);
480
+ if (isAnvilTestChain(chain.id)) {
481
+ // @note We make a time jump PAST the very first slot to not have to deal with the edge case of the first slot.
482
+ // The edge case being that the genesis block is already occupying slot 0, so we cannot have another block.
483
+ try {
484
+ // Need to get the time
485
+ const currentSlot = await rollup.getSlotNumber();
486
+ if (BigInt(currentSlot) === 0n) {
487
+ const ts = Number(await rollup.getTimestampForSlot(1n));
488
+ await rpcCall('evm_setNextBlockTimestamp', [
489
+ ts
490
+ ]);
491
+ await rpcCall('hardhat_mine', [
492
+ 1
493
+ ]);
494
+ const currentSlot = await rollup.getSlotNumber();
495
+ if (BigInt(currentSlot) !== 1n) {
496
+ throw new Error(`Error jumping time: current slot is ${currentSlot}`);
497
+ }
498
+ logger.info(`Jumped to slot 1`);
499
+ }
500
+ } catch (e) {
501
+ throw new Error(`Error jumping time: ${e}`);
502
+ }
503
+ }
504
+ return {
505
+ walletClient,
506
+ publicClient,
507
+ l1ContractAddresses: {
508
+ ...l1Contracts,
509
+ slashFactoryAddress
510
+ }
511
+ };
512
+ };
513
+ class L1Deployer {
514
+ walletClient;
515
+ publicClient;
516
+ acceleratedTestDeployments;
517
+ logger;
518
+ txUtilsConfig;
519
+ salt;
520
+ txHashes;
521
+ l1TxUtils;
522
+ constructor(walletClient, publicClient, maybeSalt, acceleratedTestDeployments = false, logger = createLogger('L1Deployer'), txUtilsConfig){
523
+ this.walletClient = walletClient;
524
+ this.publicClient = publicClient;
525
+ this.acceleratedTestDeployments = acceleratedTestDeployments;
526
+ this.logger = logger;
527
+ this.txUtilsConfig = txUtilsConfig;
528
+ this.txHashes = [];
529
+ this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), {
530
+ size: 32
531
+ }) : undefined;
532
+ this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.logger, this.txUtilsConfig, this.acceleratedTestDeployments);
533
+ }
534
+ async deploy(params, args = []) {
535
+ const { txHash, address } = await deployL1Contract(this.walletClient, this.publicClient, params.contractAbi, params.contractBytecode, args, this.salt, params.libraries, this.logger, this.l1TxUtils, this.acceleratedTestDeployments);
536
+ if (txHash) {
537
+ this.txHashes.push(txHash);
538
+ }
539
+ return address;
540
+ }
541
+ async waitForDeployments() {
542
+ if (this.acceleratedTestDeployments) {
543
+ this.logger.info('Accelerated test deployments - skipping waiting for deployments');
544
+ return;
545
+ }
546
+ if (this.txHashes.length === 0) {
547
+ return;
548
+ }
549
+ this.logger.info(`Waiting for ${this.txHashes.length} transactions to be mined...`);
550
+ await Promise.all(this.txHashes.map((txHash)=>this.publicClient.waitForTransactionReceipt({
551
+ hash: txHash
552
+ })));
553
+ this.logger.info('All transactions mined successfully');
554
+ }
555
+ sendTransaction(tx) {
556
+ return this.l1TxUtils.sendTransaction(tx);
557
+ }
558
+ }
559
+ // docs:start:deployL1Contract
560
+ /**
561
+ * Helper function to deploy ETH contracts.
562
+ * @param walletClient - A viem WalletClient.
563
+ * @param publicClient - A viem PublicClient.
564
+ * @param abi - The ETH contract's ABI (as abitype's Abi).
565
+ * @param bytecode - The ETH contract's bytecode.
566
+ * @param args - Constructor arguments for the contract.
567
+ * @param maybeSalt - Optional salt for CREATE2 deployment (does not wait for deployment tx to be mined if set, does not send tx if contract already exists).
568
+ * @returns The ETH address the contract was deployed to.
569
+ */ export async function deployL1Contract(walletClient, publicClient, abi, bytecode, args = [], maybeSalt, libraries, logger, l1TxUtils, acceleratedTestDeployments = false) {
570
+ let txHash = undefined;
571
+ let resultingAddress = undefined;
572
+ if (!l1TxUtils) {
573
+ l1TxUtils = new L1TxUtils(publicClient, walletClient, logger, undefined, acceleratedTestDeployments);
574
+ }
575
+ if (libraries) {
576
+ // Note that this does NOT work well for linked libraries having linked libraries.
577
+ // Verify that all link references have corresponding code
578
+ for(const linkRef in libraries.linkReferences){
579
+ for(const contractName in libraries.linkReferences[linkRef]){
580
+ if (!libraries.libraryCode[contractName]) {
581
+ throw new Error(`Missing library code for ${contractName}`);
582
+ }
583
+ }
584
+ }
585
+ const replacements = {};
586
+ const libraryTxs = [];
587
+ for(const libraryName in libraries?.libraryCode){
588
+ const lib = libraries.libraryCode[libraryName];
589
+ const { address, txHash } = await deployL1Contract(walletClient, publicClient, lib.contractAbi, lib.contractBytecode, [], maybeSalt, undefined, logger, l1TxUtils, acceleratedTestDeployments);
590
+ if (txHash) {
591
+ libraryTxs.push(txHash);
592
+ }
593
+ for(const linkRef in libraries.linkReferences){
594
+ for(const contractName in libraries.linkReferences[linkRef]){
595
+ // If the library name matches the one we just deployed, we replace it.
596
+ if (contractName !== libraryName) {
597
+ continue;
598
+ }
599
+ // We read the first instance to figure out what we are to replace.
600
+ const start = 2 + 2 * libraries.linkReferences[linkRef][contractName][0].start;
601
+ const length = 2 * libraries.linkReferences[linkRef][contractName][0].length;
602
+ const toReplace = bytecode.slice(start, start + length);
603
+ replacements[toReplace] = address;
604
+ }
605
+ }
606
+ }
607
+ const escapeRegExp = (s)=>{
608
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape special characters
609
+ };
610
+ for(const toReplace in replacements){
611
+ const replacement = replacements[toReplace].toString().slice(2);
612
+ bytecode = bytecode.replace(new RegExp(escapeRegExp(toReplace), 'g'), replacement);
613
+ }
614
+ // Reth fails gas estimation if the deployed contract attempts to call a library that is not yet deployed,
615
+ // so we wait for all library deployments to be mined before deploying the contract.
616
+ // However, if we are in fast mode or using debugMaxGasLimit, we will skip simulation, so we can skip waiting.
617
+ if (libraryTxs.length > 0 && !acceleratedTestDeployments) {
618
+ logger?.verbose(`Awaiting for linked libraries to be deployed`);
619
+ await Promise.all(libraryTxs.map((txHash)=>publicClient.waitForTransactionReceipt({
620
+ hash: txHash
621
+ })));
622
+ } else {
623
+ logger?.verbose(`Skipping waiting for linked libraries to be deployed ${acceleratedTestDeployments ? '(accelerated test deployments)' : ''}`);
624
+ }
625
+ }
626
+ if (maybeSalt) {
627
+ const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, maybeSalt);
628
+ resultingAddress = address;
629
+ const existing = await publicClient.getBytecode({
630
+ address: resultingAddress
631
+ });
632
+ if (existing === undefined || existing === '0x') {
633
+ const res = await l1TxUtils.sendTransaction({
634
+ to: DEPLOYER_ADDRESS,
635
+ data: concatHex([
636
+ salt,
637
+ calldata
638
+ ])
639
+ });
640
+ txHash = res.txHash;
641
+ logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
642
+ } else {
643
+ logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
644
+ }
645
+ } else {
646
+ // Regular deployment path
647
+ const deployData = encodeDeployData({
648
+ abi,
649
+ bytecode,
650
+ args
651
+ });
652
+ const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
653
+ to: null,
654
+ data: deployData
655
+ });
656
+ txHash = receipt.transactionHash;
657
+ resultingAddress = receipt.contractAddress;
658
+ if (!resultingAddress) {
659
+ throw new Error(`No contract address found in receipt: ${JSON.stringify(receipt, (_, val)=>typeof val === 'bigint' ? String(val) : val)}`);
660
+ }
661
+ }
662
+ return {
663
+ address: EthAddress.fromString(resultingAddress),
664
+ txHash
665
+ };
666
+ }
667
+ export function getExpectedAddress(abi, bytecode, args, salt) {
668
+ const paddedSalt = padHex(salt, {
669
+ size: 32
670
+ });
671
+ const calldata = encodeDeployData({
672
+ abi,
673
+ bytecode,
674
+ args
675
+ });
676
+ const address = getContractAddress({
677
+ from: DEPLOYER_ADDRESS,
678
+ salt: paddedSalt,
679
+ bytecode: calldata,
680
+ opcode: 'CREATE2'
681
+ });
682
+ return {
683
+ address,
684
+ paddedSalt,
685
+ calldata
686
+ };
687
+ } // docs:end:deployL1Contract