@aztec/ethereum 0.65.2 → 0.66.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.
@@ -5,6 +5,8 @@ import { type DebugLogger } from '@aztec/foundation/log';
5
5
  import {
6
6
  CoinIssuerAbi,
7
7
  CoinIssuerBytecode,
8
+ ExtRollupLibAbi,
9
+ ExtRollupLibBytecode,
8
10
  FeeJuicePortalAbi,
9
11
  FeeJuicePortalBytecode,
10
12
  GovernanceAbi,
@@ -13,6 +15,8 @@ import {
13
15
  GovernanceProposerBytecode,
14
16
  InboxAbi,
15
17
  InboxBytecode,
18
+ LeonidasLibAbi,
19
+ LeonidasLibBytecode,
16
20
  OutboxAbi,
17
21
  OutboxBytecode,
18
22
  RegistryAbi,
@@ -22,12 +26,8 @@ import {
22
26
  RollupAbi,
23
27
  RollupBytecode,
24
28
  RollupLinkReferences,
25
- SampleLibAbi,
26
- SampleLibBytecode,
27
29
  TestERC20Abi,
28
30
  TestERC20Bytecode,
29
- TxsDecoderAbi,
30
- TxsDecoderBytecode,
31
31
  } from '@aztec/l1-artifacts';
32
32
 
33
33
  import type { Abi, Narrow } from 'abitype';
@@ -53,8 +53,10 @@ import { type HDAccount, type PrivateKeyAccount, mnemonicToAccount, privateKeyTo
53
53
  import { foundry } from 'viem/chains';
54
54
 
55
55
  import { type L1ContractsConfig } from './config.js';
56
+ import { MINIMUM_STAKE } from './constants.js';
56
57
  import { isAnvilTestChain } from './ethereum_chain.js';
57
58
  import { type L1ContractAddresses } from './l1_contract_addresses.js';
59
+ import { L1TxUtils } from './l1_tx_utils.js';
58
60
 
59
61
  /**
60
62
  * Return type of the deployL1Contract function.
@@ -126,10 +128,14 @@ export interface L1ContractArtifactsForDeployment {
126
128
  * Rollup contract artifacts
127
129
  */
128
130
  rollup: ContractArtifacts;
131
+ /**
132
+ * The token to stake.
133
+ */
134
+ stakingAsset: ContractArtifacts;
129
135
  /**
130
136
  * The token to pay for gas. This will be bridged to L2 via the feeJuicePortal below
131
137
  */
132
- feeJuice: ContractArtifacts;
138
+ feeAsset: ContractArtifacts;
133
139
  /**
134
140
  * Fee juice portal contract artifacts. Optional for now as gas is not strictly enforced
135
141
  */
@@ -171,18 +177,22 @@ export const l1Artifacts: L1ContractArtifactsForDeployment = {
171
177
  libraries: {
172
178
  linkReferences: RollupLinkReferences,
173
179
  libraryCode: {
174
- TxsDecoder: {
175
- contractAbi: TxsDecoderAbi,
176
- contractBytecode: TxsDecoderBytecode,
180
+ LeonidasLib: {
181
+ contractAbi: LeonidasLibAbi,
182
+ contractBytecode: LeonidasLibBytecode,
177
183
  },
178
- SampleLib: {
179
- contractAbi: SampleLibAbi,
180
- contractBytecode: SampleLibBytecode,
184
+ ExtRollupLib: {
185
+ contractAbi: ExtRollupLibAbi,
186
+ contractBytecode: ExtRollupLibBytecode,
181
187
  },
182
188
  },
183
189
  },
184
190
  },
185
- feeJuice: {
191
+ stakingAsset: {
192
+ contractAbi: TestERC20Abi,
193
+ contractBytecode: TestERC20Bytecode,
194
+ },
195
+ feeAsset: {
186
196
  contractAbi: TestERC20Abi,
187
197
  contractBytecode: TestERC20Bytecode,
188
198
  },
@@ -255,6 +265,7 @@ export function createL1Clients(
255
265
  const publicClient = createPublicClient({
256
266
  chain,
257
267
  transport: http(rpcUrl),
268
+ pollingInterval: 100,
258
269
  });
259
270
 
260
271
  return { walletClient, publicClient };
@@ -306,8 +317,19 @@ export const deployL1Contracts = async (
306
317
  const registryAddress = await govDeployer.deploy(l1Artifacts.registry, [account.address.toString()]);
307
318
  logger.info(`Deployed Registry at ${registryAddress}`);
308
319
 
309
- const feeJuiceAddress = await govDeployer.deploy(l1Artifacts.feeJuice);
310
- logger.info(`Deployed Fee Juice at ${feeJuiceAddress}`);
320
+ const feeAssetAddress = await govDeployer.deploy(l1Artifacts.feeAsset, [
321
+ 'FeeJuice',
322
+ 'FEE',
323
+ account.address.toString(),
324
+ ]);
325
+ logger.info(`Deployed Fee Juice at ${feeAssetAddress}`);
326
+
327
+ const stakingAssetAddress = await govDeployer.deploy(l1Artifacts.stakingAsset, [
328
+ 'Staking',
329
+ 'STK',
330
+ account.address.toString(),
331
+ ]);
332
+ logger.info(`Deployed Staking Asset at ${stakingAssetAddress}`);
311
333
 
312
334
  // @todo #8084
313
335
  // @note These numbers are just chosen to make testing simple.
@@ -320,26 +342,29 @@ export const deployL1Contracts = async (
320
342
  ]);
321
343
  logger.info(`Deployed GovernanceProposer at ${governanceProposerAddress}`);
322
344
 
345
+ // @note @LHerskind the assets are expected to be the same at some point, but for better
346
+ // configurability they are different for now.
323
347
  const governanceAddress = await govDeployer.deploy(l1Artifacts.governance, [
324
- feeJuiceAddress.toString(),
348
+ feeAssetAddress.toString(),
325
349
  governanceProposerAddress.toString(),
326
350
  ]);
327
351
  logger.info(`Deployed Governance at ${governanceAddress}`);
328
352
 
329
353
  const coinIssuerAddress = await govDeployer.deploy(l1Artifacts.coinIssuer, [
330
- feeJuiceAddress.toString(),
354
+ feeAssetAddress.toString(),
331
355
  1n * 10n ** 18n, // @todo #8084
332
356
  governanceAddress.toString(),
333
357
  ]);
334
358
  logger.info(`Deployed CoinIssuer at ${coinIssuerAddress}`);
335
359
 
336
360
  const rewardDistributorAddress = await govDeployer.deploy(l1Artifacts.rewardDistributor, [
337
- feeJuiceAddress.toString(),
361
+ feeAssetAddress.toString(),
338
362
  registryAddress.toString(),
339
363
  governanceAddress.toString(),
340
364
  ]);
341
365
  logger.info(`Deployed RewardDistributor at ${rewardDistributorAddress}`);
342
366
 
367
+ logger.verbose(`Waiting for governance contracts to be deployed`);
343
368
  await govDeployer.waitForDeployments();
344
369
  logger.info(`All governance contracts deployed`);
345
370
 
@@ -347,27 +372,29 @@ export const deployL1Contracts = async (
347
372
 
348
373
  const feeJuicePortalAddress = await deployer.deploy(l1Artifacts.feeJuicePortal, [
349
374
  registryAddress.toString(),
350
- feeJuiceAddress.toString(),
375
+ feeAssetAddress.toString(),
351
376
  args.l2FeeJuiceAddress.toString(),
352
377
  ]);
353
378
  logger.info(`Deployed Fee Juice Portal at ${feeJuicePortalAddress}`);
354
379
 
355
- const rollupArgs = {
380
+ const rollupConfigArgs = {
356
381
  aztecSlotDuration: args.aztecSlotDuration,
357
382
  aztecEpochDuration: args.aztecEpochDuration,
358
383
  targetCommitteeSize: args.aztecTargetCommitteeSize,
359
384
  aztecEpochProofClaimWindowInL2Slots: args.aztecEpochProofClaimWindowInL2Slots,
385
+ minimumStake: MINIMUM_STAKE,
360
386
  };
361
- const rollupAddress = await deployer.deploy(l1Artifacts.rollup, [
387
+ const rollupArgs = [
362
388
  feeJuicePortalAddress.toString(),
363
389
  rewardDistributorAddress.toString(),
390
+ stakingAssetAddress.toString(),
364
391
  args.vkTreeRoot.toString(),
365
392
  args.protocolContractTreeRoot.toString(),
366
393
  account.address.toString(),
367
- args.initialValidators?.map(v => v.toString()) ?? [],
368
- rollupArgs,
369
- ]);
370
- logger.info(`Deployed Rollup at ${rollupAddress}`, rollupArgs);
394
+ rollupConfigArgs,
395
+ ];
396
+ const rollupAddress = await deployer.deploy(l1Artifacts.rollup, rollupArgs);
397
+ logger.info(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
371
398
 
372
399
  await deployer.waitForDeployments();
373
400
  logger.info(`All core contracts deployed`);
@@ -378,9 +405,15 @@ export const deployL1Contracts = async (
378
405
  client: walletClient,
379
406
  });
380
407
 
381
- const feeJuice = getContract({
382
- address: feeJuiceAddress.toString(),
383
- abi: l1Artifacts.feeJuice.contractAbi,
408
+ const feeAsset = getContract({
409
+ address: feeAssetAddress.toString(),
410
+ abi: l1Artifacts.feeAsset.contractAbi,
411
+ client: walletClient,
412
+ });
413
+
414
+ const stakingAsset = getContract({
415
+ address: stakingAssetAddress.toString(),
416
+ abi: l1Artifacts.stakingAsset.contractAbi,
384
417
  client: walletClient,
385
418
  });
386
419
 
@@ -393,12 +426,40 @@ export const deployL1Contracts = async (
393
426
  // Transaction hashes to await
394
427
  const txHashes: Hex[] = [];
395
428
 
429
+ {
430
+ const txHash = await feeAsset.write.setFreeForAll([true], {} as any);
431
+ logger.info(`Fee asset set to free for all in ${txHash}`);
432
+ txHashes.push(txHash);
433
+ }
434
+
435
+ if (args.initialValidators && args.initialValidators.length > 0) {
436
+ // Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch.
437
+ const stakeNeeded = MINIMUM_STAKE * BigInt(args.initialValidators.length);
438
+ await Promise.all(
439
+ [
440
+ await stakingAsset.write.mint([walletClient.account.address, stakeNeeded], {} as any),
441
+ await stakingAsset.write.approve([rollupAddress.toString(), stakeNeeded], {} as any),
442
+ ].map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })),
443
+ );
444
+
445
+ const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([
446
+ args.initialValidators.map(v => ({
447
+ attester: v.toString(),
448
+ proposer: v.toString(),
449
+ withdrawer: v.toString(),
450
+ amount: MINIMUM_STAKE,
451
+ })),
452
+ ]);
453
+ txHashes.push(initiateValidatorSetTxHash);
454
+ logger.info(`Initialized validator set (${args.initialValidators.join(', ')}) in tx ${initiateValidatorSetTxHash}`);
455
+ }
456
+
396
457
  // @note This value MUST match what is in `constants.nr`. It is currently specified here instead of just importing
397
458
  // because there is circular dependency hell. This is a temporary solution. #3342
398
459
  // @todo #8084
399
460
  // fund the portal contract with Fee Juice
400
- const FEE_JUICE_INITIAL_MINT = 200000000000000000000;
401
- const mintTxHash = await feeJuice.write.mint([feeJuicePortalAddress.toString(), FEE_JUICE_INITIAL_MINT], {} as any);
461
+ const FEE_JUICE_INITIAL_MINT = 200000000000000000000n;
462
+ const mintTxHash = await feeAsset.write.mint([feeJuicePortalAddress.toString(), FEE_JUICE_INITIAL_MINT], {} as any);
402
463
 
403
464
  // @note This is used to ensure we fully wait for the transaction when running against a real chain
404
465
  // otherwise we execute subsequent transactions too soon
@@ -414,7 +475,7 @@ export const deployL1Contracts = async (
414
475
  }
415
476
 
416
477
  logger.info(
417
- `Initialized Fee Juice Portal at ${feeJuicePortalAddress} to bridge between L1 ${feeJuiceAddress} to L2 ${args.l2FeeJuiceAddress}`,
478
+ `Initialized Fee Juice Portal at ${feeJuicePortalAddress} to bridge between L1 ${feeAssetAddress} to L2 ${args.l2FeeJuiceAddress}`,
418
479
  );
419
480
 
420
481
  if (isAnvilTestChain(chain.id)) {
@@ -492,7 +553,8 @@ export const deployL1Contracts = async (
492
553
  registryAddress,
493
554
  inboxAddress,
494
555
  outboxAddress,
495
- feeJuiceAddress,
556
+ feeJuiceAddress: feeAssetAddress,
557
+ stakingAssetAddress,
496
558
  feeJuicePortalAddress,
497
559
  coinIssuerAddress,
498
560
  rewardDistributorAddress,
@@ -607,10 +669,21 @@ export async function deployL1Contract(
607
669
  logger?: DebugLogger,
608
670
  ): Promise<{ address: EthAddress; txHash: Hex | undefined }> {
609
671
  let txHash: Hex | undefined = undefined;
610
- let address: Hex | null | undefined = undefined;
672
+ let resultingAddress: Hex | null | undefined = undefined;
673
+
674
+ const l1TxUtils = new L1TxUtils(publicClient, walletClient, logger);
611
675
 
612
676
  if (libraries) {
613
- // @note Assumes that we wont have nested external libraries.
677
+ // Note that this does NOT work well for linked libraries having linked libraries.
678
+
679
+ // Verify that all link references have corresponding code
680
+ for (const linkRef in libraries.linkReferences) {
681
+ for (const contractName in libraries.linkReferences[linkRef]) {
682
+ if (!libraries.libraryCode[contractName]) {
683
+ throw new Error(`Missing library code for ${contractName}`);
684
+ }
685
+ }
686
+ }
614
687
 
615
688
  const replacements: Record<string, EthAddress> = {};
616
689
 
@@ -659,21 +732,31 @@ export async function deployL1Contract(
659
732
  const salt = padHex(maybeSalt, { size: 32 });
660
733
  const deployer: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
661
734
  const calldata = encodeDeployData({ abi, bytecode, args });
662
- address = getContractAddress({ from: deployer, salt, bytecode: calldata, opcode: 'CREATE2' });
663
- const existing = await publicClient.getBytecode({ address });
735
+ resultingAddress = getContractAddress({ from: deployer, salt, bytecode: calldata, opcode: 'CREATE2' });
736
+ const existing = await publicClient.getBytecode({ address: resultingAddress });
664
737
 
665
738
  if (existing === undefined || existing === '0x') {
666
- txHash = await walletClient.sendTransaction({ to: deployer, data: concatHex([salt, calldata]) });
667
- logger?.verbose(`Deploying contract with salt ${salt} to address ${address} in tx ${txHash}`);
739
+ const res = await l1TxUtils.sendTransaction({
740
+ to: deployer,
741
+ data: concatHex([salt, calldata]),
742
+ });
743
+ txHash = res.txHash;
744
+
745
+ logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
668
746
  } else {
669
- logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${address}`);
747
+ logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
670
748
  }
671
749
  } else {
672
- txHash = await walletClient.deployContract({ abi, bytecode, args });
673
- logger?.verbose(`Deploying contract in tx ${txHash}`);
674
- const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, pollingInterval: 100 });
675
- address = receipt.contractAddress;
676
- if (!address) {
750
+ // Regular deployment path
751
+ const deployData = encodeDeployData({ abi, bytecode, args });
752
+ const receipt = await l1TxUtils.sendAndMonitorTransaction({
753
+ to: null,
754
+ data: deployData,
755
+ });
756
+
757
+ txHash = receipt.transactionHash;
758
+ resultingAddress = receipt.contractAddress;
759
+ if (!resultingAddress) {
677
760
  throw new Error(
678
761
  `No contract address found in receipt: ${JSON.stringify(receipt, (_, val) =>
679
762
  typeof val === 'bigint' ? String(val) : val,
@@ -682,6 +765,6 @@ export async function deployL1Contract(
682
765
  }
683
766
  }
684
767
 
685
- return { address: EthAddress.fromString(address!), txHash };
768
+ return { address: EthAddress.fromString(resultingAddress!), txHash };
686
769
  }
687
770
  // docs:end:deployL1Contract
@@ -0,0 +1,316 @@
1
+ import { toBigIntBE, toHex } from '@aztec/foundation/bigint-buffer';
2
+ import { keccak256 } from '@aztec/foundation/crypto';
3
+ import { type EthAddress } from '@aztec/foundation/eth-address';
4
+ import { createDebugLogger } from '@aztec/foundation/log';
5
+
6
+ import fs from 'fs';
7
+ import { type Hex } from 'viem';
8
+
9
+ /**
10
+ * A class that provides utility functions for interacting with ethereum (L1).
11
+ */
12
+ export class EthCheatCodes {
13
+ constructor(
14
+ /**
15
+ * The RPC URL to use for interacting with the chain
16
+ */
17
+ public rpcUrl: string,
18
+ /**
19
+ * The logger to use for the eth cheatcodes
20
+ */
21
+ public logger = createDebugLogger('aztec:cheat_codes:eth'),
22
+ ) {}
23
+
24
+ async rpcCall(method: string, params: any[]) {
25
+ const paramsString = JSON.stringify(params);
26
+ const content = {
27
+ body: `{"jsonrpc":"2.0", "method": "${method}", "params": ${paramsString}, "id": 1}`,
28
+ method: 'POST',
29
+ headers: { 'Content-Type': 'application/json' },
30
+ };
31
+ return await (await fetch(this.rpcUrl, content)).json();
32
+ }
33
+
34
+ /**
35
+ * Get the auto mine status of the underlying chain
36
+ * @returns True if automine is on, false otherwise
37
+ */
38
+ public async isAutoMining(): Promise<boolean> {
39
+ try {
40
+ const res = await this.rpcCall('anvil_getAutomine', []);
41
+ return res.result;
42
+ } catch (err) {
43
+ this.logger.error(`Calling "anvil_getAutomine" failed with:`, err);
44
+ }
45
+ return false;
46
+ }
47
+
48
+ /**
49
+ * Get the current blocknumber
50
+ * @returns The current block number
51
+ */
52
+ public async blockNumber(): Promise<number> {
53
+ const res = await this.rpcCall('eth_blockNumber', []);
54
+ return parseInt(res.result, 16);
55
+ }
56
+
57
+ /**
58
+ * Get the current chainId
59
+ * @returns The current chainId
60
+ */
61
+ public async chainId(): Promise<number> {
62
+ const res = await this.rpcCall('eth_chainId', []);
63
+ return parseInt(res.result, 16);
64
+ }
65
+
66
+ /**
67
+ * Get the current timestamp
68
+ * @returns The current timestamp
69
+ */
70
+ public async timestamp(): Promise<number> {
71
+ const res = await this.rpcCall('eth_getBlockByNumber', ['latest', true]);
72
+ return parseInt(res.result.timestamp, 16);
73
+ }
74
+
75
+ /**
76
+ * Advance the chain by a number of blocks
77
+ * @param numberOfBlocks - The number of blocks to mine
78
+ */
79
+ public async mine(numberOfBlocks = 1): Promise<void> {
80
+ const res = await this.rpcCall('hardhat_mine', [numberOfBlocks]);
81
+ if (res.error) {
82
+ throw new Error(`Error mining: ${res.error.message}`);
83
+ }
84
+ this.logger.verbose(`Mined ${numberOfBlocks} L1 blocks`);
85
+ }
86
+
87
+ /**
88
+ * Mines a single block with evm_mine
89
+ */
90
+ public async evmMine(): Promise<void> {
91
+ const res = await this.rpcCall('evm_mine', []);
92
+ if (res.error) {
93
+ throw new Error(`Error mining: ${res.error.message}`);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Set the balance of an account
99
+ * @param account - The account to set the balance for
100
+ * @param balance - The balance to set
101
+ */
102
+ public async setBalance(account: EthAddress, balance: bigint): Promise<void> {
103
+ const res = await this.rpcCall('anvil_setBalance', [account.toString(), toHex(balance)]);
104
+ if (res.error) {
105
+ throw new Error(`Error setting balance for ${account}: ${res.error.message}`);
106
+ }
107
+ this.logger.verbose(`Set balance for ${account} to ${balance}`);
108
+ }
109
+
110
+ /**
111
+ * Set the interval between blocks (block time)
112
+ * @param interval - The interval to use between blocks
113
+ */
114
+ public async setBlockInterval(interval: number): Promise<void> {
115
+ const res = await this.rpcCall('anvil_setBlockTimestampInterval', [interval]);
116
+ if (res.error) {
117
+ throw new Error(`Error setting block interval: ${res.error.message}`);
118
+ }
119
+ this.logger.verbose(`Set L1 block interval to ${interval}`);
120
+ }
121
+
122
+ /**
123
+ * Set the next block base fee per gas
124
+ * @param baseFee - The base fee to set
125
+ */
126
+ public async setNextBlockBaseFeePerGas(baseFee: bigint): Promise<void> {
127
+ const res = await this.rpcCall('anvil_setNextBlockBaseFeePerGas', [baseFee.toString()]);
128
+ if (res.error) {
129
+ throw new Error(`Error setting next block base fee per gas: ${res.error.message}`);
130
+ }
131
+ this.logger.verbose(`Set L1 next block base fee per gas to ${baseFee}`);
132
+ }
133
+
134
+ /**
135
+ * Set the interval between blocks (block time)
136
+ * @param seconds - The interval to use between blocks
137
+ */
138
+ public async setIntervalMining(seconds: number): Promise<void> {
139
+ const res = await this.rpcCall('anvil_setIntervalMining', [seconds]);
140
+ if (res.error) {
141
+ throw new Error(`Error setting interval mining: ${res.error.message}`);
142
+ }
143
+ this.logger.verbose(`Set L1 interval mining to ${seconds} seconds`);
144
+ }
145
+
146
+ /**
147
+ * Set the automine status of the underlying anvil chain
148
+ * @param automine - The automine status to set
149
+ */
150
+ public async setAutomine(automine: boolean): Promise<void> {
151
+ const res = await this.rpcCall('anvil_setAutomine', [automine]);
152
+ if (res.error) {
153
+ throw new Error(`Error setting automine: ${res.error.message}`);
154
+ }
155
+ this.logger.verbose(`Set L1 automine to ${automine}`);
156
+ }
157
+
158
+ /**
159
+ * Drop a transaction from the mempool
160
+ * @param txHash - The transaction hash
161
+ */
162
+ public async dropTransaction(txHash: Hex): Promise<void> {
163
+ const res = await this.rpcCall('anvil_dropTransaction', [txHash]);
164
+ if (res.error) {
165
+ throw new Error(`Error dropping transaction: ${res.error.message}`);
166
+ }
167
+ this.logger.verbose(`Dropped transaction ${txHash}`);
168
+ }
169
+
170
+ /**
171
+ * Set the next block timestamp
172
+ * @param timestamp - The timestamp to set the next block to
173
+ */
174
+ public async setNextBlockTimestamp(timestamp: number): Promise<void> {
175
+ const res = await this.rpcCall('evm_setNextBlockTimestamp', [timestamp]);
176
+ if (res.error) {
177
+ throw new Error(`Error setting next block timestamp: ${res.error.message}`);
178
+ }
179
+ this.logger.verbose(`Set L1 next block timestamp to ${timestamp}`);
180
+ }
181
+
182
+ /**
183
+ * Set the next block timestamp and mines the block
184
+ * @param timestamp - The timestamp to set the next block to
185
+ */
186
+ public async warp(timestamp: number | bigint): Promise<void> {
187
+ const res = await this.rpcCall('evm_setNextBlockTimestamp', [Number(timestamp)]);
188
+ if (res.error) {
189
+ throw new Error(`Error warping: ${res.error.message}`);
190
+ }
191
+ await this.mine();
192
+ this.logger.verbose(`Warped L1 timestamp to ${timestamp}`);
193
+ }
194
+
195
+ /**
196
+ * Dumps the current chain state to a file.
197
+ * @param fileName - The file name to dump state into
198
+ */
199
+ public async dumpChainState(fileName: string): Promise<void> {
200
+ const res = await this.rpcCall('hardhat_dumpState', []);
201
+ if (res.error) {
202
+ throw new Error(`Error dumping state: ${res.error.message}`);
203
+ }
204
+ const jsonContent = JSON.stringify(res.result);
205
+ fs.writeFileSync(`${fileName}.json`, jsonContent, 'utf8');
206
+ this.logger.verbose(`Dumped state to ${fileName}`);
207
+ }
208
+
209
+ /**
210
+ * Loads the chain state from a file.
211
+ * @param fileName - The file name to load state from
212
+ */
213
+ public async loadChainState(fileName: string): Promise<void> {
214
+ const data = JSON.parse(fs.readFileSync(`${fileName}.json`, 'utf8'));
215
+ const res = await this.rpcCall('hardhat_loadState', [data]);
216
+ if (res.error) {
217
+ throw new Error(`Error loading state: ${res.error.message}`);
218
+ }
219
+ this.logger.verbose(`Loaded state from ${fileName}`);
220
+ }
221
+
222
+ /**
223
+ * Load the value at a storage slot of a contract address on eth
224
+ * @param contract - The contract address
225
+ * @param slot - The storage slot
226
+ * @returns - The value at the storage slot
227
+ */
228
+ public async load(contract: EthAddress, slot: bigint): Promise<bigint> {
229
+ const res = await this.rpcCall('eth_getStorageAt', [contract.toString(), toHex(slot), 'latest']);
230
+ return BigInt(res.result);
231
+ }
232
+
233
+ /**
234
+ * Set the value at a storage slot of a contract address on eth
235
+ * @param contract - The contract address
236
+ * @param slot - The storage slot
237
+ * @param value - The value to set the storage slot to
238
+ */
239
+ public async store(contract: EthAddress, slot: bigint, value: bigint): Promise<void> {
240
+ // for the rpc call, we need to change value to be a 32 byte hex string.
241
+ const res = await this.rpcCall('hardhat_setStorageAt', [contract.toString(), toHex(slot), toHex(value, true)]);
242
+ if (res.error) {
243
+ throw new Error(`Error setting storage for contract ${contract} at ${slot}: ${res.error.message}`);
244
+ }
245
+ this.logger.verbose(`Set L1 storage for contract ${contract} at ${slot} to ${value}`);
246
+ }
247
+
248
+ /**
249
+ * Computes the slot value for a given map and key.
250
+ * @param baseSlot - The base slot of the map (specified in Aztec.nr contract)
251
+ * @param key - The key to lookup in the map
252
+ * @returns The storage slot of the value in the map
253
+ */
254
+ public keccak256(baseSlot: bigint, key: bigint): bigint {
255
+ // abi encode (removing the 0x) - concat key and baseSlot (both padded to 32 bytes)
256
+ const abiEncoded = toHex(key, true).substring(2) + toHex(baseSlot, true).substring(2);
257
+ return toBigIntBE(keccak256(Buffer.from(abiEncoded, 'hex')));
258
+ }
259
+
260
+ /**
261
+ * Send transactions impersonating an externally owned account or contract.
262
+ * @param who - The address to impersonate
263
+ */
264
+ public async startImpersonating(who: EthAddress | Hex): Promise<void> {
265
+ const res = await this.rpcCall('hardhat_impersonateAccount', [who.toString()]);
266
+ if (res.error) {
267
+ throw new Error(`Error impersonating ${who}: ${res.error.message}`);
268
+ }
269
+ this.logger.verbose(`Impersonating ${who}`);
270
+ }
271
+
272
+ /**
273
+ * Stop impersonating an account that you are currently impersonating.
274
+ * @param who - The address to stop impersonating
275
+ */
276
+ public async stopImpersonating(who: EthAddress | Hex): Promise<void> {
277
+ const res = await this.rpcCall('hardhat_stopImpersonatingAccount', [who.toString()]);
278
+ if (res.error) {
279
+ throw new Error(`Error when stopping the impersonation of ${who}: ${res.error.message}`);
280
+ }
281
+ this.logger.verbose(`Stopped impersonating ${who}`);
282
+ }
283
+
284
+ /**
285
+ * Set the bytecode for a contract
286
+ * @param contract - The contract address
287
+ * @param bytecode - The bytecode to set
288
+ */
289
+ public async etch(contract: EthAddress, bytecode: `0x${string}`): Promise<void> {
290
+ const res = await this.rpcCall('hardhat_setCode', [contract.toString(), bytecode]);
291
+ if (res.error) {
292
+ throw new Error(`Error setting bytecode for ${contract}: ${res.error.message}`);
293
+ }
294
+ this.logger.verbose(`Set bytecode for ${contract} to ${bytecode}`);
295
+ }
296
+
297
+ /**
298
+ * Get the bytecode for a contract
299
+ * @param contract - The contract address
300
+ * @returns The bytecode for the contract
301
+ */
302
+ public async getBytecode(contract: EthAddress): Promise<`0x${string}`> {
303
+ const res = await this.rpcCall('eth_getCode', [contract.toString(), 'latest']);
304
+ return res.result;
305
+ }
306
+
307
+ /**
308
+ * Get the raw transaction object for a given transaction hash
309
+ * @param txHash - The transaction hash
310
+ * @returns The raw transaction
311
+ */
312
+ public async getRawTransaction(txHash: Hex): Promise<`0x${string}`> {
313
+ const res = await this.rpcCall('debug_getRawTransaction', [txHash]);
314
+ return res.result;
315
+ }
316
+ }
package/src/index.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  export * from './constants.js';
2
2
  export * from './deploy_l1_contracts.js';
3
+ export * from './ethereum_chain.js';
4
+ export * from './eth_cheat_codes.js';
5
+ export * from './l1_tx_utils.js';
3
6
  export * from './l1_contract_addresses.js';
4
7
  export * from './l1_reader.js';
5
- export * from './ethereum_chain.js';
6
8
  export * from './utils.js';
7
9
  export * from './config.js';
8
10
  export * from './types.js';
11
+ export * from './contracts/index.js';
@@ -20,6 +20,7 @@ export const L1ContractsNames = [
20
20
  'rewardDistributorAddress',
21
21
  'governanceProposerAddress',
22
22
  'governanceAddress',
23
+ 'stakingAssetAddress',
23
24
  ] as const;
24
25
 
25
26
  /** Provides the directory of current L1 contract addresses */
@@ -33,6 +34,7 @@ export const L1ContractAddressesSchema = z.object({
33
34
  inboxAddress: schemas.EthAddress,
34
35
  outboxAddress: schemas.EthAddress,
35
36
  feeJuiceAddress: schemas.EthAddress,
37
+ stakingAssetAddress: schemas.EthAddress,
36
38
  feeJuicePortalAddress: schemas.EthAddress,
37
39
  coinIssuerAddress: schemas.EthAddress,
38
40
  rewardDistributorAddress: schemas.EthAddress,
@@ -68,6 +70,11 @@ export const l1ContractAddressesMapping: ConfigMappingsType<L1ContractAddresses>
68
70
  description: 'The deployed L1 Fee Juice contract address.',
69
71
  parseEnv,
70
72
  },
73
+ stakingAssetAddress: {
74
+ env: 'STAKING_ASSET_CONTRACT_ADDRESS',
75
+ description: 'The deployed L1 staking asset contract address.',
76
+ parseEnv,
77
+ },
71
78
  feeJuicePortalAddress: {
72
79
  env: 'FEE_JUICE_PORTAL_CONTRACT_ADDRESS',
73
80
  description: 'The deployed L1 Fee Juice portal contract address.',