@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,86 @@
1
+ import { Blob } from '@aztec/blob-lib';
2
+
3
+ import { formatGwei } from 'viem';
4
+
5
+ import { type GasPrice, L1TxUtils } from './l1_tx_utils.js';
6
+
7
+ export class L1TxUtilsWithBlobs extends L1TxUtils {
8
+ /**
9
+ * Attempts to cancel a transaction by sending a 0-value tx to self with same nonce but higher gas prices
10
+ * @param nonce - The nonce of the transaction to cancel
11
+ * @param previousGasPrice - The gas price of the previous transaction
12
+ * @param attempts - The number of attempts to cancel the transaction
13
+ * @returns The hash of the cancellation transaction
14
+ */
15
+ override async attemptTxCancellation(nonce: number, isBlobTx = false, previousGasPrice?: GasPrice, attempts = 0) {
16
+ const account = this.walletClient.account;
17
+
18
+ // Get gas price with higher priority fee for cancellation
19
+ const cancelGasPrice = await this.getGasPrice(
20
+ {
21
+ ...this.config,
22
+ // Use high bump for cancellation to ensure it replaces the original tx
23
+ priorityFeeRetryBumpPercentage: 150, // 150% bump should be enough to replace any tx
24
+ },
25
+ isBlobTx,
26
+ attempts + 1,
27
+ previousGasPrice,
28
+ );
29
+
30
+ this.logger?.debug(`Attempting to cancel transaction with nonce ${nonce}`, {
31
+ maxFeePerGas: formatGwei(cancelGasPrice.maxFeePerGas),
32
+ maxPriorityFeePerGas: formatGwei(cancelGasPrice.maxPriorityFeePerGas),
33
+ });
34
+ const request = {
35
+ to: account.address,
36
+ value: 0n,
37
+ };
38
+
39
+ // Send 0-value tx to self with higher gas price
40
+ if (!isBlobTx) {
41
+ const cancelTxHash = await this.walletClient.sendTransaction({
42
+ ...request,
43
+ nonce,
44
+ gas: 21_000n, // Standard ETH transfer gas
45
+ maxFeePerGas: cancelGasPrice.maxFeePerGas,
46
+ maxPriorityFeePerGas: cancelGasPrice.maxPriorityFeePerGas,
47
+ });
48
+ const receipt = await this.monitorTransaction(
49
+ request,
50
+ cancelTxHash,
51
+ { gasLimit: 21_000n },
52
+ undefined,
53
+ undefined,
54
+ true,
55
+ );
56
+
57
+ return receipt.transactionHash;
58
+ } else {
59
+ const blobData = new Uint8Array(131072).fill(0);
60
+ const kzg = Blob.getViemKzgInstance();
61
+ const blobInputs = {
62
+ blobs: [blobData],
63
+ kzg,
64
+ maxFeePerBlobGas: cancelGasPrice.maxFeePerBlobGas!,
65
+ };
66
+ const cancelTxHash = await this.walletClient.sendTransaction({
67
+ ...request,
68
+ ...blobInputs,
69
+ nonce,
70
+ gas: 21_000n,
71
+ maxFeePerGas: cancelGasPrice.maxFeePerGas,
72
+ maxPriorityFeePerGas: cancelGasPrice.maxPriorityFeePerGas,
73
+ });
74
+ const receipt = await this.monitorTransaction(
75
+ request,
76
+ cancelTxHash,
77
+ { gasLimit: 21_000n },
78
+ undefined,
79
+ blobInputs,
80
+ true,
81
+ );
82
+
83
+ return receipt.transactionHash;
84
+ }
85
+ }
86
+ }
package/src/queries.ts ADDED
@@ -0,0 +1,58 @@
1
+ import type { EthAddress } from '@aztec/foundation/eth-address';
2
+
3
+ import type { L1ContractsConfig } from './config.js';
4
+ import { GovernanceContract } from './contracts/governance.js';
5
+ import { RollupContract } from './contracts/rollup.js';
6
+ import type { ViemPublicClient } from './types.js';
7
+
8
+ /** Reads the L1ContractsConfig from L1 contracts. */
9
+ export async function getL1ContractsConfig(
10
+ publicClient: ViemPublicClient,
11
+ addresses: { governanceAddress: EthAddress; rollupAddress?: EthAddress },
12
+ ): Promise<Omit<L1ContractsConfig, 'ethereumSlotDuration'> & { l1StartBlock: bigint; l1GenesisTime: bigint }> {
13
+ const governance = new GovernanceContract(addresses.governanceAddress.toString(), publicClient, undefined);
14
+ const governanceProposer = await governance.getProposer();
15
+ const rollupAddress = addresses.rollupAddress ?? (await governance.getGovernanceAddresses()).rollupAddress;
16
+ const rollup = new RollupContract(publicClient, rollupAddress.toString());
17
+ const slasherProposer = await rollup.getSlashingProposer();
18
+
19
+ const [
20
+ l1StartBlock,
21
+ l1GenesisTime,
22
+ aztecEpochDuration,
23
+ aztecProofSubmissionWindow,
24
+ aztecSlotDuration,
25
+ aztecTargetCommitteeSize,
26
+ minimumStake,
27
+ governanceProposerQuorum,
28
+ governanceProposerRoundSize,
29
+ slashingQuorum,
30
+ slashingRoundSize,
31
+ ] = await Promise.all([
32
+ rollup.getL1StartBlock(),
33
+ rollup.getL1GenesisTime(),
34
+ rollup.getEpochDuration(),
35
+ rollup.getProofSubmissionWindow(),
36
+ rollup.getSlotDuration(),
37
+ rollup.getTargetCommitteeSize(),
38
+ rollup.getMinimumStake(),
39
+ governanceProposer.getQuorumSize(),
40
+ governanceProposer.getRoundSize(),
41
+ slasherProposer.getQuorumSize(),
42
+ slasherProposer.getRoundSize(),
43
+ ] as const);
44
+
45
+ return {
46
+ l1StartBlock,
47
+ l1GenesisTime,
48
+ aztecEpochDuration: Number(aztecEpochDuration),
49
+ aztecProofSubmissionWindow: Number(aztecProofSubmissionWindow),
50
+ aztecSlotDuration: Number(aztecSlotDuration),
51
+ aztecTargetCommitteeSize: Number(aztecTargetCommitteeSize),
52
+ governanceProposerQuorum: Number(governanceProposerQuorum),
53
+ governanceProposerRoundSize: Number(governanceProposerRoundSize),
54
+ minimumStake,
55
+ slashingQuorum: Number(slashingQuorum),
56
+ slashingRoundSize: Number(slashingRoundSize),
57
+ };
58
+ }
@@ -0,0 +1,24 @@
1
+ import { L1TxUtilsWithBlobs } from '../l1_tx_utils_with_blobs.js';
2
+ import { type Delayer, withDelayer } from './tx_delayer.js';
3
+
4
+ export class DelayedTxUtils extends L1TxUtilsWithBlobs {
5
+ public delayer: Delayer | undefined;
6
+
7
+ public static fromL1TxUtils(l1TxUtils: L1TxUtilsWithBlobs, ethereumSlotDuration: number) {
8
+ const { client, delayer } = withDelayer(l1TxUtils.walletClient, {
9
+ ethereumSlotDuration,
10
+ });
11
+ const casted = l1TxUtils as unknown as DelayedTxUtils;
12
+ casted.delayer = delayer;
13
+ casted.walletClient = client;
14
+ return casted;
15
+ }
16
+
17
+ public enableDelayer(ethereumSlotDuration: number) {
18
+ const { client, delayer } = withDelayer(this.walletClient, {
19
+ ethereumSlotDuration,
20
+ });
21
+ this.delayer = delayer;
22
+ this.walletClient = client;
23
+ }
24
+ }
@@ -0,0 +1,38 @@
1
+ import fs from 'fs';
2
+
3
+ import { EthCheatCodes } from '../eth_cheat_codes.js';
4
+
5
+ /**
6
+ * A class that provides utility functions for interacting with ethereum (L1) dumping/loading state to/from a file.
7
+ * It is separated to avoid importing fs in the main EthCheatCodes class, which might be used in the browser.
8
+ */
9
+ export class EthCheatCodesWithState extends EthCheatCodes {
10
+ /**
11
+ * Dumps the current chain state to a file.
12
+ * @param fileName - The file name to dump state into
13
+ */
14
+ public async dumpChainState(fileName: string): Promise<void> {
15
+ let res: any;
16
+ try {
17
+ res = await this.rpcCall('hardhat_dumpState', []);
18
+ } catch (e) {
19
+ throw new Error(`Error dumping state: ${e}`);
20
+ }
21
+ fs.writeFileSync(`${fileName}.json`, res, 'utf8');
22
+ this.logger.verbose(`Dumped state to ${fileName}`);
23
+ }
24
+
25
+ /**
26
+ * Loads the chain state from a file.
27
+ * @param fileName - The file name to load state from
28
+ */
29
+ public async loadChainState(fileName: string): Promise<void> {
30
+ const data = JSON.parse(fs.readFileSync(`${fileName}.json`, 'utf8'));
31
+ try {
32
+ await this.rpcCall('hardhat_loadState', [data]);
33
+ } catch (e) {
34
+ throw new Error(`Error loading state: ${e}`);
35
+ }
36
+ this.logger.verbose(`Loaded state from ${fileName}`);
37
+ }
38
+ }
@@ -0,0 +1,5 @@
1
+ export * from './delayed_tx_utils.js';
2
+ export * from './eth_cheat_codes_with_state.js';
3
+ export * from './start_anvil.js';
4
+ export * from './tx_delayer.js';
5
+ export * from './upgrade_utils.js';
@@ -0,0 +1,52 @@
1
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
2
+ import { fileURLToPath } from '@aztec/foundation/url';
3
+
4
+ import { type Anvil, createAnvil } from '@viem/anvil';
5
+ import { dirname, resolve } from 'path';
6
+
7
+ /**
8
+ * Ensures there's a running Anvil instance and returns the RPC URL.
9
+ */
10
+ export async function startAnvil(
11
+ opts: {
12
+ l1BlockTime?: number;
13
+ } = {},
14
+ ): Promise<{ anvil: Anvil; rpcUrl: string; stop: () => Promise<void> }> {
15
+ const anvilBinary = resolve(dirname(fileURLToPath(import.meta.url)), '../../', 'scripts/anvil_kill_wrapper.sh');
16
+
17
+ let port: number | undefined;
18
+
19
+ // Start anvil.
20
+ // We go via a wrapper script to ensure if the parent dies, anvil dies.
21
+ const anvil = await retry(
22
+ async () => {
23
+ const anvil = createAnvil({
24
+ anvilBinary,
25
+ port: 0,
26
+ blockTime: opts.l1BlockTime,
27
+ stopTimeout: 1000,
28
+ });
29
+
30
+ // Listen to the anvil output to get the port.
31
+ const removeHandler = anvil.on('message', (message: string) => {
32
+ if (port === undefined && message.includes('Listening on')) {
33
+ port = parseInt(message.match(/Listening on ([^:]+):(\d+)/)![2]);
34
+ }
35
+ });
36
+ await anvil.start();
37
+ removeHandler();
38
+
39
+ return anvil;
40
+ },
41
+ 'Start anvil',
42
+ makeBackoff([5, 5, 5]),
43
+ );
44
+
45
+ if (!port) {
46
+ throw new Error('Failed to start anvil');
47
+ }
48
+
49
+ // Monkeypatch the anvil instance to include the actually assigned port
50
+ Object.defineProperty(anvil, 'port', { value: port, writable: false });
51
+ return { anvil, stop: () => anvil.stop(), rpcUrl: `http://127.0.0.1:${port}` };
52
+ }
@@ -0,0 +1,163 @@
1
+ import { omit } from '@aztec/foundation/collection';
2
+ import { type Logger, createLogger } from '@aztec/foundation/log';
3
+ import { retryUntil } from '@aztec/foundation/retry';
4
+
5
+ import { inspect } from 'util';
6
+ import {
7
+ type Client,
8
+ type Hex,
9
+ type PublicClient,
10
+ keccak256,
11
+ parseTransaction,
12
+ publicActions,
13
+ walletActions,
14
+ } from 'viem';
15
+
16
+ import type { ViemWalletClient } from '../types.js';
17
+
18
+ export function waitUntilBlock<T extends Client>(client: T, blockNumber: number | bigint, logger?: Logger) {
19
+ const publicClient =
20
+ 'getBlockNumber' in client && typeof client.getBlockNumber === 'function'
21
+ ? (client as unknown as PublicClient)
22
+ : client.extend(publicActions);
23
+
24
+ return retryUntil(
25
+ async () => {
26
+ const currentBlockNumber = await publicClient.getBlockNumber({ cacheTime: 0 });
27
+ logger?.debug(`Block number is ${currentBlockNumber} (waiting until ${blockNumber})`);
28
+ return currentBlockNumber >= BigInt(blockNumber);
29
+ },
30
+ `Wait until L1 block ${blockNumber}`,
31
+ 120,
32
+ 0.1,
33
+ );
34
+ }
35
+
36
+ export function waitUntilL1Timestamp<T extends Client>(client: T, timestamp: number | bigint, logger?: Logger) {
37
+ const publicClient =
38
+ 'getBlockNumber' in client && typeof client.getBlockNumber === 'function'
39
+ ? (client as unknown as PublicClient)
40
+ : client.extend(publicActions);
41
+
42
+ let lastBlock: bigint | undefined = undefined;
43
+ return retryUntil(
44
+ async () => {
45
+ const currentBlockNumber = await publicClient.getBlockNumber({ cacheTime: 0 });
46
+ if (currentBlockNumber === lastBlock) {
47
+ return false;
48
+ }
49
+ lastBlock = currentBlockNumber;
50
+ const currentBlock = await publicClient.getBlock({ includeTransactions: false, blockNumber: currentBlockNumber });
51
+ const currentTs = currentBlock.timestamp;
52
+ logger?.debug(`Block timstamp is ${currentTs} (waiting until ${timestamp})`);
53
+ return currentTs >= BigInt(timestamp);
54
+ },
55
+ `Wait until L1 timestamp ${timestamp}`,
56
+ 120,
57
+ 0.1,
58
+ );
59
+ }
60
+
61
+ export interface Delayer {
62
+ /** Returns the list of all txs (not just the delayed ones) sent through the attached client. */
63
+ getTxs(): Hex[];
64
+ /** Delays the next tx to be sent so it lands on the given L1 block number. */
65
+ pauseNextTxUntilBlock(l1BlockNumber: number | bigint | undefined): void;
66
+ /** Delays the next tx to be sent so it lands on the given timestamp. */
67
+ pauseNextTxUntilTimestamp(l1Timestamp: number | bigint | undefined): void;
68
+ }
69
+
70
+ class DelayerImpl implements Delayer {
71
+ constructor(opts: { ethereumSlotDuration: bigint | number }) {
72
+ this.ethereumSlotDuration = BigInt(opts.ethereumSlotDuration);
73
+ }
74
+
75
+ public ethereumSlotDuration: bigint;
76
+ public nextWait: { l1Timestamp: bigint } | { l1BlockNumber: bigint } | undefined = undefined;
77
+ public txs: Hex[] = [];
78
+
79
+ getTxs() {
80
+ return this.txs;
81
+ }
82
+
83
+ pauseNextTxUntilBlock(l1BlockNumber: number | bigint) {
84
+ this.nextWait = { l1BlockNumber: BigInt(l1BlockNumber) };
85
+ }
86
+
87
+ pauseNextTxUntilTimestamp(l1Timestamp: number | bigint) {
88
+ this.nextWait = { l1Timestamp: BigInt(l1Timestamp) };
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Returns a new client (without modifying the one passed in) with an injected tx delayer.
94
+ * The delayer can be used to hold off the next tx to be sent until a given block number.
95
+ * TODO(#10824): This doesn't play along well with blob txs for some reason.
96
+ */
97
+ export function withDelayer<T extends ViemWalletClient>(
98
+ client: T,
99
+ opts: { ethereumSlotDuration: bigint | number },
100
+ ): { client: T; delayer: Delayer } {
101
+ const logger = createLogger('ethereum:tx_delayer');
102
+ const delayer = new DelayerImpl(opts);
103
+ const extended = client
104
+ // Tweak sendRawTransaction so it uses the delay defined in the delayer.
105
+ // Note that this will only work with local accounts (ie accounts for which we have the private key).
106
+ // Transactions signed by the node will not be delayed since they use sendTransaction directly,
107
+ // but we do not use them in our codebase at all.
108
+ .extend(client => ({
109
+ async sendRawTransaction(...args) {
110
+ if (delayer.nextWait !== undefined) {
111
+ const waitUntil = delayer.nextWait;
112
+ delayer.nextWait = undefined;
113
+
114
+ const publicClient = client as unknown as PublicClient;
115
+ const wait =
116
+ 'l1BlockNumber' in waitUntil
117
+ ? waitUntilBlock(publicClient, waitUntil.l1BlockNumber - 1n, logger)
118
+ : waitUntilL1Timestamp(publicClient, waitUntil.l1Timestamp - delayer.ethereumSlotDuration, logger);
119
+
120
+ // Compute the tx hash manually so we emulate sendRawTransaction response
121
+ const { serializedTransaction } = args[0];
122
+ const txHash = keccak256(serializedTransaction);
123
+ logger.info(`Delaying tx ${txHash} until ${inspect(waitUntil)}`, {
124
+ argsLen: args.length,
125
+ ...omit(parseTransaction(serializedTransaction), 'data', 'sidecars'),
126
+ });
127
+
128
+ // Do not await here so we can return the tx hash immediately as if it had been sent on the spot.
129
+ // Instead, delay it so it lands on the desired block number or timestamp, assuming anvil will
130
+ // mine it immediately.
131
+ void wait
132
+ .then(async () => {
133
+ const clientTxHash = await client.sendRawTransaction(...args);
134
+ if (clientTxHash !== txHash) {
135
+ logger.error(`Tx hash returned by the client does not match computed one`, {
136
+ clientTxHash,
137
+ computedTxHash: txHash,
138
+ });
139
+ }
140
+ logger.info(`Sent previously delayed tx ${clientTxHash} to land on ${inspect(waitUntil)}`);
141
+ delayer.txs.push(clientTxHash);
142
+ })
143
+ .catch(err => logger.error(`Error sending tx after delay`, err));
144
+
145
+ return Promise.resolve(txHash);
146
+ } else {
147
+ const txHash = await client.sendRawTransaction(...args);
148
+ logger.verbose(`Sent tx immediately ${txHash}`);
149
+ delayer.txs.push(txHash);
150
+ return txHash;
151
+ }
152
+ },
153
+ }))
154
+ // Re-extend with sendTransaction so it uses the modified sendRawTransaction.
155
+ .extend(client => ({ sendTransaction: walletActions(client).sendTransaction }))
156
+ // And with the actions that depend on the modified sendTransaction
157
+ .extend(client => ({
158
+ writeContract: walletActions(client).writeContract,
159
+ deployContract: walletActions(client).deployContract,
160
+ })) as T;
161
+
162
+ return { client: extended, delayer };
163
+ }
@@ -0,0 +1,100 @@
1
+ import type { Logger } from '@aztec/foundation/log';
2
+ import { TestERC20Abi as FeeJuiceAbi } from '@aztec/l1-artifacts';
3
+ import { GovernanceAbi } from '@aztec/l1-artifacts/GovernanceAbi';
4
+
5
+ import { type GetContractReturnType, type PrivateKeyAccount, getContract } from 'viem';
6
+
7
+ import { EthCheatCodes } from '../eth_cheat_codes.js';
8
+ import type { L1ContractAddresses } from '../l1_contract_addresses.js';
9
+ import type { L1Clients } from '../types.js';
10
+
11
+ export async function executeGovernanceProposal(
12
+ proposalId: bigint,
13
+ governance: GetContractReturnType<typeof GovernanceAbi, L1Clients['publicClient']>,
14
+ voteAmount: bigint,
15
+ privateKey: PrivateKeyAccount,
16
+ publicClient: L1Clients['publicClient'],
17
+ walletClient: L1Clients['walletClient'],
18
+ rpcUrls: string[],
19
+ logger: Logger,
20
+ ) {
21
+ const proposal = await governance.read.getProposal([proposalId]);
22
+
23
+ const waitL1Block = async () => {
24
+ await publicClient.waitForTransactionReceipt({
25
+ hash: await walletClient.sendTransaction({
26
+ to: privateKey.address,
27
+ value: 1n,
28
+ account: privateKey,
29
+ }),
30
+ });
31
+ };
32
+
33
+ const cheatCodes = new EthCheatCodes(rpcUrls, logger);
34
+
35
+ const timeToActive = proposal.creation + proposal.config.votingDelay;
36
+ logger.info(`Warping to ${timeToActive + 1n}`);
37
+ await cheatCodes.warp(Number(timeToActive + 1n));
38
+ logger.info(`Warped to ${timeToActive + 1n}`);
39
+ await waitL1Block();
40
+
41
+ logger.info(`Voting`);
42
+ const voteTx = await governance.write.vote([proposalId, voteAmount, true], { account: privateKey });
43
+ await publicClient.waitForTransactionReceipt({ hash: voteTx });
44
+ logger.info(`Voted`);
45
+
46
+ const timeToExecutable = timeToActive + proposal.config.votingDuration + proposal.config.executionDelay + 1n;
47
+ logger.info(`Warping to ${timeToExecutable}`);
48
+ await cheatCodes.warp(Number(timeToExecutable));
49
+ logger.info(`Warped to ${timeToExecutable}`);
50
+ await waitL1Block();
51
+
52
+ const executeTx = await governance.write.execute([proposalId], { account: privateKey });
53
+ await publicClient.waitForTransactionReceipt({ hash: executeTx });
54
+ logger.info(`Executed proposal`);
55
+ }
56
+
57
+ export async function createGovernanceProposal(
58
+ payloadAddress: `0x${string}`,
59
+ addresses: L1ContractAddresses,
60
+ privateKey: PrivateKeyAccount,
61
+ publicClient: L1Clients['publicClient'],
62
+ logger: Logger,
63
+ ): Promise<{ governance: GetContractReturnType<typeof GovernanceAbi, L1Clients['publicClient']>; voteAmount: bigint }> {
64
+ const token = getContract({
65
+ address: addresses.feeJuiceAddress.toString(),
66
+ abi: FeeJuiceAbi,
67
+ client: publicClient,
68
+ });
69
+
70
+ const governance = getContract({
71
+ address: addresses.governanceAddress.toString(),
72
+ abi: GovernanceAbi,
73
+ client: publicClient,
74
+ });
75
+
76
+ const lockAmount = 10000n * 10n ** 18n;
77
+ const voteAmount = 10000n * 10n ** 18n;
78
+
79
+ const mintTx = await token.write.mint([privateKey.address, lockAmount + voteAmount], { account: privateKey });
80
+ await publicClient.waitForTransactionReceipt({ hash: mintTx });
81
+ logger.info(`Minted tokens`);
82
+
83
+ const approveTx = await token.write.approve([addresses.governanceAddress.toString(), lockAmount + voteAmount], {
84
+ account: privateKey,
85
+ });
86
+ await publicClient.waitForTransactionReceipt({ hash: approveTx });
87
+ logger.info(`Approved tokens`);
88
+
89
+ const depositTx = await governance.write.deposit([privateKey.address, lockAmount + voteAmount], {
90
+ account: privateKey,
91
+ });
92
+ await publicClient.waitForTransactionReceipt({ hash: depositTx });
93
+ logger.info(`Deposited tokens`);
94
+
95
+ await governance.write.proposeWithLock([payloadAddress, privateKey.address], {
96
+ account: privateKey,
97
+ });
98
+
99
+ return { governance, voteAmount };
100
+ }
package/src/types.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type {
2
+ Account,
3
+ Chain,
4
+ Client,
5
+ FallbackTransport,
6
+ HttpTransport,
7
+ PublicActions,
8
+ PublicClient,
9
+ PublicRpcSchema,
10
+ WalletActions,
11
+ WalletClient,
12
+ WalletRpcSchema,
13
+ } from 'viem';
14
+
15
+ /** Type for a viem public client */
16
+ export type ViemPublicClient = PublicClient<FallbackTransport<HttpTransport[]>, Chain>;
17
+
18
+ export type SimpleViemWalletClient = WalletClient<FallbackTransport<HttpTransport[]>, Chain, Account>;
19
+
20
+ export type ExtendedViemWalletClient = Client<
21
+ FallbackTransport<readonly HttpTransport[]>,
22
+ Chain,
23
+ Account,
24
+ [...PublicRpcSchema, ...WalletRpcSchema],
25
+ PublicActions<FallbackTransport<readonly HttpTransport[]>, Chain> & WalletActions<Chain, Account>
26
+ >;
27
+
28
+ export type ViemWalletClient = SimpleViemWalletClient | ExtendedViemWalletClient;
29
+
30
+ export type L1Clients = {
31
+ publicClient: ViemPublicClient;
32
+ walletClient: ExtendedViemWalletClient;
33
+ };