@aztec/ethereum 0.77.1 → 0.78.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.
@@ -1,6 +1,6 @@
1
1
  import { EthAddress } from '@aztec/foundation/eth-address';
2
2
  import type { Fr } from '@aztec/foundation/fields';
3
- import type { Logger } from '@aztec/foundation/log';
3
+ import { type Logger, createLogger } from '@aztec/foundation/log';
4
4
  import {
5
5
  CoinIssuerAbi,
6
6
  CoinIssuerBytecode,
@@ -43,6 +43,7 @@ import {
43
43
  createPublicClient,
44
44
  createWalletClient,
45
45
  encodeDeployData,
46
+ encodeFunctionData,
46
47
  fallback,
47
48
  getAddress,
48
49
  getContract,
@@ -60,7 +61,13 @@ import type { L1ContractsConfig } from './config.js';
60
61
  import { RegistryContract } from './contracts/registry.js';
61
62
  import { RollupContract } from './contracts/rollup.js';
62
63
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
63
- import { L1TxUtils, type L1TxUtilsConfig, defaultL1TxUtilsConfig } from './l1_tx_utils.js';
64
+ import {
65
+ type GasPrice,
66
+ type L1TxRequest,
67
+ L1TxUtils,
68
+ type L1TxUtilsConfig,
69
+ defaultL1TxUtilsConfig,
70
+ } from './l1_tx_utils.js';
64
71
  import type { L1Clients, ViemPublicClient, ViemWalletClient } from './types.js';
65
72
 
66
73
  export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
@@ -204,6 +211,8 @@ export interface DeployL1ContractsArgs extends L1ContractsConfig {
204
211
  initialValidators?: EthAddress[];
205
212
  /** Configuration for the L1 tx utils module. */
206
213
  l1TxConfig?: Partial<L1TxUtilsConfig>;
214
+ /** Enable fast mode for deployments (fire and forget transactions) */
215
+ acceleratedTestDeployments?: boolean;
207
216
  }
208
217
 
209
218
  /**
@@ -255,7 +264,14 @@ export const deployRollupAndPeriphery = async (
255
264
  logger: Logger,
256
265
  txUtilsConfig: L1TxUtilsConfig,
257
266
  ) => {
258
- const deployer = new L1Deployer(clients.walletClient, clients.publicClient, args.salt, logger, txUtilsConfig);
267
+ const deployer = new L1Deployer(
268
+ clients.walletClient,
269
+ clients.publicClient,
270
+ args.salt,
271
+ args.acceleratedTestDeployments,
272
+ logger,
273
+ txUtilsConfig,
274
+ );
259
275
 
260
276
  const addresses = await RegistryContract.collectAddresses(clients.publicClient, registryAddress, 'canonical');
261
277
 
@@ -337,31 +353,48 @@ export const deployRollup = async (
337
353
 
338
354
  if (args.initialValidators && args.initialValidators.length > 0) {
339
355
  // Check if some of the initial validators are already registered, so we support idempotent deployments
340
- const validatorsInfo = await Promise.all(
341
- args.initialValidators.map(async address => ({ address, ...(await rollup.read.getInfo([address.toString()])) })),
342
- );
343
- const existingValidators = validatorsInfo.filter(v => v.status !== 0);
344
- if (existingValidators.length > 0) {
345
- logger.warn(
346
- `Validators ${existingValidators.map(v => v.address).join(', ')} already exist. Skipping from initialization.`,
356
+ let newValidatorsAddresses = args.initialValidators.map(v => v.toString());
357
+ if (!args.acceleratedTestDeployments) {
358
+ const validatorsInfo = await Promise.all(
359
+ args.initialValidators.map(async address => ({
360
+ address,
361
+ ...(await rollup.read.getInfo([address.toString()])),
362
+ })),
347
363
  );
348
- }
364
+ const existingValidators = validatorsInfo.filter(v => v.status !== 0);
365
+ if (existingValidators.length > 0) {
366
+ logger.warn(
367
+ `Validators ${existingValidators
368
+ .map(v => v.address)
369
+ .join(', ')} already exist. Skipping from initialization.`,
370
+ );
371
+ }
349
372
 
350
- const newValidatorsAddresses = validatorsInfo.filter(v => v.status === 0).map(v => v.address.toString());
373
+ newValidatorsAddresses = validatorsInfo.filter(v => v.status === 0).map(v => v.address.toString());
374
+ }
351
375
 
352
376
  if (newValidatorsAddresses.length > 0) {
353
- const stakingAsset = getContract({
354
- address: addresses.stakingAssetAddress.toString(),
355
- abi: l1Artifacts.stakingAsset.contractAbi,
356
- client: clients.walletClient,
357
- });
358
377
  // Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch.
359
378
  const stakeNeeded = args.minimumStake * BigInt(newValidatorsAddresses.length);
360
379
  await Promise.all(
361
380
  [
362
- await stakingAsset.write.mint([clients.walletClient.account.address, stakeNeeded], {} as any),
363
- await stakingAsset.write.approve([rollupAddress.toString(), stakeNeeded], {} as any),
364
- ].map(txHash => clients.publicClient.waitForTransactionReceipt({ hash: txHash })),
381
+ await deployer.sendTransaction({
382
+ to: addresses.stakingAssetAddress.toString(),
383
+ data: encodeFunctionData({
384
+ abi: l1Artifacts.stakingAsset.contractAbi,
385
+ functionName: 'mint',
386
+ args: [clients.walletClient.account.address, stakeNeeded],
387
+ }),
388
+ }),
389
+ await deployer.sendTransaction({
390
+ to: addresses.stakingAssetAddress.toString(),
391
+ data: encodeFunctionData({
392
+ abi: l1Artifacts.stakingAsset.contractAbi,
393
+ functionName: 'approve',
394
+ args: [rollupAddress.toString(), stakeNeeded],
395
+ }),
396
+ }),
397
+ ].map(tx => clients.publicClient.waitForTransactionReceipt({ hash: tx.txHash })),
365
398
  );
366
399
 
367
400
  const validators = newValidatorsAddresses.map(v => ({
@@ -370,7 +403,13 @@ export const deployRollup = async (
370
403
  withdrawer: v,
371
404
  amount: args.minimumStake,
372
405
  }));
373
- const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([validators]);
406
+ // const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([validators]);
407
+ const initiateValidatorSetTxHash = await deployer.walletClient.writeContract({
408
+ address: rollupAddress.toString(),
409
+ abi: l1Artifacts.rollup.contractAbi,
410
+ functionName: 'cheat__InitialiseValidatorSet',
411
+ args: [validators],
412
+ });
374
413
  txHashes.push(initiateValidatorSetTxHash);
375
414
  logger.info(`Initialized validator set`, {
376
415
  validators,
@@ -401,9 +440,11 @@ export const deployL1Contracts = async (
401
440
  args: DeployL1ContractsArgs,
402
441
  txUtilsConfig: L1TxUtilsConfig = defaultL1TxUtilsConfig,
403
442
  ): Promise<DeployL1ContractsReturnType> => {
443
+ const clients = createL1Clients(rpcUrls, account, chain);
444
+ const { walletClient, publicClient } = clients;
445
+
404
446
  // We are assuming that you are running this on a local anvil node which have 1s block times
405
447
  // To align better with actual deployment, we update the block interval to 12s
406
- const { walletClient, publicClient } = createL1Clients(rpcUrls, account, chain);
407
448
 
408
449
  const rpcCall = async (method: string, params: any[]) => {
409
450
  logger.info(`Calling ${method} with params: ${JSON.stringify(params)}`);
@@ -425,7 +466,14 @@ export const deployL1Contracts = async (
425
466
  logger.verbose(`Deploying contracts from ${account.address.toString()}`);
426
467
 
427
468
  // Governance stuff
428
- const deployer = new L1Deployer(walletClient, publicClient, args.salt, logger, txUtilsConfig);
469
+ const deployer = new L1Deployer(
470
+ walletClient,
471
+ publicClient,
472
+ args.salt,
473
+ args.acceleratedTestDeployments,
474
+ logger,
475
+ txUtilsConfig,
476
+ );
429
477
 
430
478
  const registryAddress = await deployer.deploy(l1Artifacts.registry, [account.address.toString()]);
431
479
  logger.verbose(`Deployed Registry at ${registryAddress}`);
@@ -494,14 +542,28 @@ export const deployL1Contracts = async (
494
542
  // Transaction hashes to await
495
543
  const txHashes: Hex[] = [];
496
544
 
497
- if (!(await feeAsset.read.freeForAll())) {
498
- const txHash = await feeAsset.write.setFreeForAll([true], {} as any);
545
+ if (args.acceleratedTestDeployments || !(await feeAsset.read.freeForAll())) {
546
+ const { txHash } = await deployer.sendTransaction({
547
+ to: feeAssetAddress.toString(),
548
+ data: encodeFunctionData({
549
+ abi: l1Artifacts.feeAsset.contractAbi,
550
+ functionName: 'setFreeForAll',
551
+ args: [true],
552
+ }),
553
+ });
499
554
  logger.verbose(`Fee asset set to free for all in ${txHash}`);
500
555
  txHashes.push(txHash);
501
556
  }
502
557
 
503
- if ((await feeAsset.read.owner()) !== getAddress(coinIssuerAddress.toString())) {
504
- const txHash = await feeAsset.write.transferOwnership([coinIssuerAddress.toString()], { account });
558
+ if (args.acceleratedTestDeployments || (await feeAsset.read.owner()) !== getAddress(coinIssuerAddress.toString())) {
559
+ const { txHash } = await deployer.sendTransaction({
560
+ to: feeAssetAddress.toString(),
561
+ data: encodeFunctionData({
562
+ abi: l1Artifacts.feeAsset.contractAbi,
563
+ functionName: 'transferOwnership',
564
+ args: [coinIssuerAddress.toString()],
565
+ }),
566
+ });
505
567
  logger.verbose(`Fee asset transferred ownership to coin issuer in ${txHash}`);
506
568
  txHashes.push(txHash);
507
569
  }
@@ -511,21 +573,47 @@ export const deployL1Contracts = async (
511
573
  // @todo #8084
512
574
  // fund the portal contract with Fee Juice
513
575
  const FEE_JUICE_INITIAL_MINT = 200000000000000000000000n;
514
- const mintTxHash = await feeAsset.write.mint([feeJuicePortalAddress.toString(), FEE_JUICE_INITIAL_MINT], {} as any);
576
+
577
+ // In fast mode, use the L1TxUtils to send transactions with nonce management
578
+ const { txHash: mintTxHash } = await deployer.sendTransaction({
579
+ to: feeAssetAddress.toString(),
580
+ data: encodeFunctionData({
581
+ abi: l1Artifacts.feeAsset.contractAbi,
582
+ functionName: 'mint',
583
+ args: [feeJuicePortalAddress.toString(), FEE_JUICE_INITIAL_MINT],
584
+ }),
585
+ });
586
+ logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash} (accelerated test deployments)`);
587
+ txHashes.push(mintTxHash);
515
588
 
516
589
  // @note This is used to ensure we fully wait for the transaction when running against a real chain
517
590
  // otherwise we execute subsequent transactions too soon
518
- await publicClient.waitForTransactionReceipt({ hash: mintTxHash });
519
- logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash}`);
591
+ if (!args.acceleratedTestDeployments) {
592
+ await publicClient.waitForTransactionReceipt({ hash: mintTxHash });
593
+ logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash}`);
594
+ }
595
+
596
+ // Check if portal needs initialization
597
+ let needsInitialization = args.acceleratedTestDeployments;
598
+ if (!args.acceleratedTestDeployments) {
599
+ // Only check if not in fast mode and not already known to need initialization
600
+ needsInitialization = !(await feeJuicePortal.read.initialized());
601
+ }
602
+ if (needsInitialization) {
603
+ const { txHash: initPortalTxHash } = await deployer.sendTransaction({
604
+ to: feeJuicePortalAddress.toString(),
605
+ data: encodeFunctionData({
606
+ abi: l1Artifacts.feeJuicePortal.contractAbi,
607
+ functionName: 'initialize',
608
+ args: [],
609
+ }),
610
+ });
520
611
 
521
- if (!(await feeJuicePortal.read.initialized())) {
522
- const initPortalTxHash = await feeJuicePortal.write.initialize();
523
612
  txHashes.push(initPortalTxHash);
524
613
  logger.verbose(`Fee juice portal initializing in tx ${initPortalTxHash}`);
525
614
  } else {
526
615
  logger.verbose(`Fee juice portal is already initialized`);
527
616
  }
528
-
529
617
  logger.verbose(
530
618
  `Initialized Fee Juice Portal at ${feeJuicePortalAddress} to bridge between L1 ${feeAssetAddress} to L2 ${args.l2FeeJuiceAddress}`,
531
619
  );
@@ -553,8 +641,18 @@ export const deployL1Contracts = async (
553
641
  client: walletClient,
554
642
  });
555
643
 
556
- if (!(await registryContract.read.isRollupRegistered([getAddress(rollup.address.toString())]))) {
557
- const upgradeTxHash = await registryContract.write.upgrade([getAddress(rollup.address.toString())], { account });
644
+ if (
645
+ args.acceleratedTestDeployments ||
646
+ !(await registryContract.read.isRollupRegistered([getAddress(rollup.address.toString())]))
647
+ ) {
648
+ const { txHash: upgradeTxHash } = await deployer.sendTransaction({
649
+ to: registryAddress.toString(),
650
+ data: encodeFunctionData({
651
+ abi: l1Artifacts.registry.contractAbi,
652
+ functionName: 'upgrade',
653
+ args: [getAddress(rollup.address.toString())],
654
+ }),
655
+ });
558
656
  logger.verbose(
559
657
  `Upgrading registry contract at ${registryAddress} to rollup ${rollup.address} in tx ${upgradeTxHash}`,
560
658
  );
@@ -564,13 +662,19 @@ export const deployL1Contracts = async (
564
662
  }
565
663
 
566
664
  // If the owner is not the Governance contract, transfer ownership to the Governance contract
567
- if ((await registryContract.read.owner()) !== getAddress(governanceAddress.toString())) {
568
- const transferOwnershipTxHash = await registryContract.write.transferOwnership(
569
- [getAddress(governanceAddress.toString())],
570
- {
571
- account,
572
- },
573
- );
665
+ if (
666
+ args.acceleratedTestDeployments ||
667
+ (await registryContract.read.owner()) !== getAddress(governanceAddress.toString())
668
+ ) {
669
+ // TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
670
+ const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
671
+ to: registryAddress.toString(),
672
+ data: encodeFunctionData({
673
+ abi: l1Artifacts.registry.contractAbi,
674
+ functionName: 'transferOwnership',
675
+ args: [getAddress(governanceAddress.toString())],
676
+ }),
677
+ });
574
678
  logger.verbose(
575
679
  `Transferring the ownership of the registry contract at ${registryAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
576
680
  );
@@ -620,16 +724,24 @@ export const deployL1Contracts = async (
620
724
  class L1Deployer {
621
725
  private salt: Hex | undefined;
622
726
  private txHashes: Hex[] = [];
623
- private l1TxUtils: L1TxUtils;
727
+ public readonly l1TxUtils: L1TxUtils;
728
+
624
729
  constructor(
625
- private walletClient: ViemWalletClient,
730
+ public readonly walletClient: ViemWalletClient,
626
731
  private publicClient: ViemPublicClient,
627
732
  maybeSalt: number | undefined,
628
- private logger: Logger,
733
+ private acceleratedTestDeployments: boolean = false,
734
+ private logger: Logger = createLogger('L1Deployer'),
629
735
  private txUtilsConfig?: L1TxUtilsConfig,
630
736
  ) {
631
737
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
632
- this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.logger, this.txUtilsConfig);
738
+ this.l1TxUtils = new L1TxUtils(
739
+ this.publicClient,
740
+ this.walletClient,
741
+ this.logger,
742
+ this.txUtilsConfig,
743
+ this.acceleratedTestDeployments,
744
+ );
633
745
  }
634
746
 
635
747
  async deploy(params: ContractArtifacts, args: readonly unknown[] = []): Promise<EthAddress> {
@@ -643,6 +755,7 @@ class L1Deployer {
643
755
  params.libraries,
644
756
  this.logger,
645
757
  this.l1TxUtils,
758
+ this.acceleratedTestDeployments,
646
759
  );
647
760
  if (txHash) {
648
761
  this.txHashes.push(txHash);
@@ -651,7 +764,21 @@ class L1Deployer {
651
764
  }
652
765
 
653
766
  async waitForDeployments(): Promise<void> {
767
+ if (this.acceleratedTestDeployments) {
768
+ this.logger.info('Accelerated test deployments - skipping waiting for deployments');
769
+ return;
770
+ }
771
+ if (this.txHashes.length === 0) {
772
+ return;
773
+ }
774
+
775
+ this.logger.info(`Waiting for ${this.txHashes.length} transactions to be mined...`);
654
776
  await Promise.all(this.txHashes.map(txHash => this.publicClient.waitForTransactionReceipt({ hash: txHash })));
777
+ this.logger.info('All transactions mined successfully');
778
+ }
779
+
780
+ sendTransaction(tx: L1TxRequest): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
781
+ return this.l1TxUtils.sendTransaction(tx);
655
782
  }
656
783
  }
657
784
 
@@ -675,14 +802,14 @@ export async function deployL1Contract(
675
802
  maybeSalt?: Hex,
676
803
  libraries?: Libraries,
677
804
  logger?: Logger,
678
- _l1TxUtils?: L1TxUtils,
805
+ l1TxUtils?: L1TxUtils,
806
+ acceleratedTestDeployments: boolean = false,
679
807
  ): Promise<{ address: EthAddress; txHash: Hex | undefined }> {
680
808
  let txHash: Hex | undefined = undefined;
681
809
  let resultingAddress: Hex | null | undefined = undefined;
682
- let l1TxUtils: L1TxUtils | undefined = _l1TxUtils;
683
810
 
684
811
  if (!l1TxUtils) {
685
- l1TxUtils = new L1TxUtils(publicClient, walletClient, logger);
812
+ l1TxUtils = new L1TxUtils(publicClient, walletClient, logger, undefined, acceleratedTestDeployments);
686
813
  }
687
814
 
688
815
  if (libraries) {
@@ -712,6 +839,7 @@ export async function deployL1Contract(
712
839
  undefined,
713
840
  logger,
714
841
  l1TxUtils,
842
+ acceleratedTestDeployments,
715
843
  );
716
844
 
717
845
  if (txHash) {
@@ -746,9 +874,16 @@ export async function deployL1Contract(
746
874
 
747
875
  // Reth fails gas estimation if the deployed contract attempts to call a library that is not yet deployed,
748
876
  // so we wait for all library deployments to be mined before deploying the contract.
749
- if (libraryTxs.length > 0) {
877
+ // However, if we are in fast mode or using debugMaxGasLimit, we will skip simulation, so we can skip waiting.
878
+ if (libraryTxs.length > 0 && !acceleratedTestDeployments) {
750
879
  logger?.verbose(`Awaiting for linked libraries to be deployed`);
751
880
  await Promise.all(libraryTxs.map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })));
881
+ } else {
882
+ logger?.verbose(
883
+ `Skipping waiting for linked libraries to be deployed ${
884
+ acceleratedTestDeployments ? '(accelerated test deployments)' : ''
885
+ }`,
886
+ );
752
887
  }
753
888
  }
754
889
 
@@ -5,7 +5,7 @@ import {
5
5
  getDefaultConfig,
6
6
  numberConfigHelper,
7
7
  } from '@aztec/foundation/config';
8
- import type { Logger } from '@aztec/foundation/log';
8
+ import { type Logger, createLogger } from '@aztec/foundation/log';
9
9
  import { makeBackoff, retry } from '@aztec/foundation/retry';
10
10
  import { sleep } from '@aztec/foundation/sleep';
11
11
 
@@ -190,14 +190,15 @@ export type TransactionStats = {
190
190
  };
191
191
 
192
192
  export class L1TxUtils {
193
- protected readonly config: L1TxUtilsConfig;
193
+ public readonly config: L1TxUtilsConfig;
194
194
  private interrupted = false;
195
195
 
196
196
  constructor(
197
197
  public publicClient: ViemPublicClient,
198
198
  public walletClient: ViemWalletClient,
199
- protected readonly logger?: Logger,
199
+ protected logger: Logger = createLogger('L1TxUtils'),
200
200
  config?: Partial<L1TxUtilsConfig>,
201
+ private debugMaxGasLimit: boolean = false,
201
202
  ) {
202
203
  this.config = {
203
204
  ...defaultL1TxUtilsConfig,
@@ -248,7 +249,9 @@ export class L1TxUtils {
248
249
  const account = this.walletClient.account;
249
250
  let gasLimit: bigint;
250
251
 
251
- if (gasConfig.gasLimit) {
252
+ if (this.debugMaxGasLimit) {
253
+ gasLimit = LARGE_GAS_LIMIT;
254
+ } else if (gasConfig.gasLimit) {
252
255
  gasLimit = gasConfig.gasLimit;
253
256
  } else {
254
257
  gasLimit = await this.estimateGas(account, request);
@@ -288,7 +291,9 @@ export class L1TxUtils {
288
291
  return { txHash, gasLimit, gasPrice };
289
292
  } catch (err: any) {
290
293
  const viemError = formatViemError(err);
291
- this.logger?.error(`Failed to send L1 transaction`, viemError.message, { metaMessages: viemError.metaMessages });
294
+ this.logger?.error(`Failed to send L1 transaction`, viemError.message, {
295
+ metaMessages: viemError.metaMessages,
296
+ });
292
297
  throw viemError;
293
298
  }
294
299
  }