@aztec/ethereum 0.66.0 → 0.67.1-devnet
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.
- package/dest/deploy_l1_contracts.d.ts +3 -3
- package/dest/deploy_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_l1_contracts.js +32 -24
- package/dest/eth_cheat_codes.d.ts +1 -10
- package/dest/eth_cheat_codes.d.ts.map +1 -1
- package/dest/eth_cheat_codes.js +8 -31
- package/dest/l1_tx_utils.d.ts +16 -5
- package/dest/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils.js +47 -18
- package/dest/test/eth_cheat_codes_with_state.d.ts +18 -0
- package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -0
- package/dest/test/eth_cheat_codes_with_state.js +34 -0
- package/dest/test/index.d.ts +1 -0
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +2 -1
- package/dest/test/tx_delayer.d.ts +4 -3
- package/dest/test/tx_delayer.d.ts.map +1 -1
- package/dest/test/tx_delayer.js +19 -8
- package/dest/utils.d.ts +2 -2
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +1 -1
- package/package.json +7 -3
- package/src/deploy_l1_contracts.ts +42 -27
- package/src/eth_cheat_codes.ts +8 -32
- package/src/l1_tx_utils.ts +64 -20
- package/src/test/eth_cheat_codes_with_state.ts +36 -0
- package/src/test/index.ts +1 -0
- package/src/test/tx_delayer.ts +20 -8
- package/src/utils.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AztecAddress } from '@aztec/foundation/aztec-address';
|
|
2
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
3
|
import { type Fr } from '@aztec/foundation/fields';
|
|
4
|
-
import { type
|
|
4
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
5
5
|
import {
|
|
6
6
|
CoinIssuerAbi,
|
|
7
7
|
CoinIssuerBytecode,
|
|
@@ -284,7 +284,7 @@ export const deployL1Contracts = async (
|
|
|
284
284
|
rpcUrl: string,
|
|
285
285
|
account: HDAccount | PrivateKeyAccount,
|
|
286
286
|
chain: Chain,
|
|
287
|
-
logger:
|
|
287
|
+
logger: Logger,
|
|
288
288
|
args: DeployL1ContractsArgs,
|
|
289
289
|
): Promise<DeployL1Contracts> => {
|
|
290
290
|
// We are assuming that you are running this on a local anvil node which have 1s block times
|
|
@@ -304,10 +304,10 @@ export const deployL1Contracts = async (
|
|
|
304
304
|
if (res.error) {
|
|
305
305
|
throw new Error(`Error setting block interval: ${res.error.message}`);
|
|
306
306
|
}
|
|
307
|
-
logger.
|
|
307
|
+
logger.warn(`Set block interval to ${args.ethereumSlotDuration}`);
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
logger.
|
|
310
|
+
logger.verbose(`Deploying contracts from ${account.address.toString()}`);
|
|
311
311
|
|
|
312
312
|
const walletClient = createWalletClient({ account, chain, transport: http(rpcUrl) });
|
|
313
313
|
const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
|
|
@@ -315,21 +315,21 @@ export const deployL1Contracts = async (
|
|
|
315
315
|
const govDeployer = new L1Deployer(walletClient, publicClient, args.salt, logger);
|
|
316
316
|
|
|
317
317
|
const registryAddress = await govDeployer.deploy(l1Artifacts.registry, [account.address.toString()]);
|
|
318
|
-
logger.
|
|
318
|
+
logger.verbose(`Deployed Registry at ${registryAddress}`);
|
|
319
319
|
|
|
320
320
|
const feeAssetAddress = await govDeployer.deploy(l1Artifacts.feeAsset, [
|
|
321
321
|
'FeeJuice',
|
|
322
322
|
'FEE',
|
|
323
323
|
account.address.toString(),
|
|
324
324
|
]);
|
|
325
|
-
logger.
|
|
325
|
+
logger.verbose(`Deployed Fee Juice at ${feeAssetAddress}`);
|
|
326
326
|
|
|
327
327
|
const stakingAssetAddress = await govDeployer.deploy(l1Artifacts.stakingAsset, [
|
|
328
328
|
'Staking',
|
|
329
329
|
'STK',
|
|
330
330
|
account.address.toString(),
|
|
331
331
|
]);
|
|
332
|
-
logger.
|
|
332
|
+
logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
|
|
333
333
|
|
|
334
334
|
// @todo #8084
|
|
335
335
|
// @note These numbers are just chosen to make testing simple.
|
|
@@ -340,7 +340,7 @@ export const deployL1Contracts = async (
|
|
|
340
340
|
quorumSize,
|
|
341
341
|
roundSize,
|
|
342
342
|
]);
|
|
343
|
-
logger.
|
|
343
|
+
logger.verbose(`Deployed GovernanceProposer at ${governanceProposerAddress}`);
|
|
344
344
|
|
|
345
345
|
// @note @LHerskind the assets are expected to be the same at some point, but for better
|
|
346
346
|
// configurability they are different for now.
|
|
@@ -348,25 +348,25 @@ export const deployL1Contracts = async (
|
|
|
348
348
|
feeAssetAddress.toString(),
|
|
349
349
|
governanceProposerAddress.toString(),
|
|
350
350
|
]);
|
|
351
|
-
logger.
|
|
351
|
+
logger.verbose(`Deployed Governance at ${governanceAddress}`);
|
|
352
352
|
|
|
353
353
|
const coinIssuerAddress = await govDeployer.deploy(l1Artifacts.coinIssuer, [
|
|
354
354
|
feeAssetAddress.toString(),
|
|
355
355
|
1n * 10n ** 18n, // @todo #8084
|
|
356
356
|
governanceAddress.toString(),
|
|
357
357
|
]);
|
|
358
|
-
logger.
|
|
358
|
+
logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
|
|
359
359
|
|
|
360
360
|
const rewardDistributorAddress = await govDeployer.deploy(l1Artifacts.rewardDistributor, [
|
|
361
361
|
feeAssetAddress.toString(),
|
|
362
362
|
registryAddress.toString(),
|
|
363
363
|
governanceAddress.toString(),
|
|
364
364
|
]);
|
|
365
|
-
logger.
|
|
365
|
+
logger.verbose(`Deployed RewardDistributor at ${rewardDistributorAddress}`);
|
|
366
366
|
|
|
367
367
|
logger.verbose(`Waiting for governance contracts to be deployed`);
|
|
368
368
|
await govDeployer.waitForDeployments();
|
|
369
|
-
logger.
|
|
369
|
+
logger.verbose(`All governance contracts deployed`);
|
|
370
370
|
|
|
371
371
|
const deployer = new L1Deployer(walletClient, publicClient, args.salt, logger);
|
|
372
372
|
|
|
@@ -375,7 +375,7 @@ export const deployL1Contracts = async (
|
|
|
375
375
|
feeAssetAddress.toString(),
|
|
376
376
|
args.l2FeeJuiceAddress.toString(),
|
|
377
377
|
]);
|
|
378
|
-
logger.
|
|
378
|
+
logger.verbose(`Deployed Fee Juice Portal at ${feeJuicePortalAddress}`);
|
|
379
379
|
|
|
380
380
|
const rollupConfigArgs = {
|
|
381
381
|
aztecSlotDuration: args.aztecSlotDuration,
|
|
@@ -394,10 +394,10 @@ export const deployL1Contracts = async (
|
|
|
394
394
|
rollupConfigArgs,
|
|
395
395
|
];
|
|
396
396
|
const rollupAddress = await deployer.deploy(l1Artifacts.rollup, rollupArgs);
|
|
397
|
-
logger.
|
|
397
|
+
logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
|
|
398
398
|
|
|
399
399
|
await deployer.waitForDeployments();
|
|
400
|
-
logger.
|
|
400
|
+
logger.verbose(`All core contracts have been deployed`);
|
|
401
401
|
|
|
402
402
|
const feeJuicePortal = getContract({
|
|
403
403
|
address: feeJuicePortalAddress.toString(),
|
|
@@ -428,13 +428,23 @@ export const deployL1Contracts = async (
|
|
|
428
428
|
|
|
429
429
|
{
|
|
430
430
|
const txHash = await feeAsset.write.setFreeForAll([true], {} as any);
|
|
431
|
-
logger.
|
|
431
|
+
logger.verbose(`Fee asset set to free for all in ${txHash}`);
|
|
432
432
|
txHashes.push(txHash);
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
|
|
435
|
+
const attesters = (await rollup.read
|
|
436
|
+
.getAttesters([])
|
|
437
|
+
.then(attesters =>
|
|
438
|
+
(attesters as `0x${string}`[]).map(attester => EthAddress.fromString(attester.toString())),
|
|
439
|
+
)) as EthAddress[];
|
|
440
|
+
|
|
441
|
+
logger.debug(`Existing attesters`, attesters);
|
|
442
|
+
|
|
443
|
+
const newAttesters = (args.initialValidators ?? []).filter(v => !attesters.some(a => a.equals(v)));
|
|
444
|
+
|
|
445
|
+
if (newAttesters.length > 0) {
|
|
436
446
|
// Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch.
|
|
437
|
-
const stakeNeeded = MINIMUM_STAKE * BigInt(
|
|
447
|
+
const stakeNeeded = MINIMUM_STAKE * BigInt(newAttesters.length);
|
|
438
448
|
await Promise.all(
|
|
439
449
|
[
|
|
440
450
|
await stakingAsset.write.mint([walletClient.account.address, stakeNeeded], {} as any),
|
|
@@ -442,8 +452,10 @@ export const deployL1Contracts = async (
|
|
|
442
452
|
].map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })),
|
|
443
453
|
);
|
|
444
454
|
|
|
455
|
+
logger.info(`Minted ${newAttesters.length} validators`);
|
|
456
|
+
|
|
445
457
|
const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([
|
|
446
|
-
|
|
458
|
+
newAttesters.map(v => ({
|
|
447
459
|
attester: v.toString(),
|
|
448
460
|
proposer: v.toString(),
|
|
449
461
|
withdrawer: v.toString(),
|
|
@@ -451,7 +463,7 @@ export const deployL1Contracts = async (
|
|
|
451
463
|
})),
|
|
452
464
|
]);
|
|
453
465
|
txHashes.push(initiateValidatorSetTxHash);
|
|
454
|
-
logger.info(`Initialized validator set (${
|
|
466
|
+
logger.info(`Initialized validator set (${newAttesters.join(', ')}) in tx ${initiateValidatorSetTxHash}`);
|
|
455
467
|
}
|
|
456
468
|
|
|
457
469
|
// @note This value MUST match what is in `constants.nr`. It is currently specified here instead of just importing
|
|
@@ -464,7 +476,7 @@ export const deployL1Contracts = async (
|
|
|
464
476
|
// @note This is used to ensure we fully wait for the transaction when running against a real chain
|
|
465
477
|
// otherwise we execute subsequent transactions too soon
|
|
466
478
|
await publicClient.waitForTransactionReceipt({ hash: mintTxHash });
|
|
467
|
-
logger.
|
|
479
|
+
logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash}`);
|
|
468
480
|
|
|
469
481
|
if (!(await feeJuicePortal.read.initialized([]))) {
|
|
470
482
|
const initPortalTxHash = await feeJuicePortal.write.initialize([]);
|
|
@@ -474,7 +486,7 @@ export const deployL1Contracts = async (
|
|
|
474
486
|
logger.verbose(`Fee juice portal is already initialized`);
|
|
475
487
|
}
|
|
476
488
|
|
|
477
|
-
logger.
|
|
489
|
+
logger.verbose(
|
|
478
490
|
`Initialized Fee Juice Portal at ${feeJuicePortalAddress} to bridge between L1 ${feeAssetAddress} to L2 ${args.l2FeeJuiceAddress}`,
|
|
479
491
|
);
|
|
480
492
|
|
|
@@ -504,15 +516,15 @@ export const deployL1Contracts = async (
|
|
|
504
516
|
// Set initial blocks as proven if requested
|
|
505
517
|
if (args.assumeProvenThrough && args.assumeProvenThrough > 0) {
|
|
506
518
|
await rollup.write.setAssumeProvenThroughBlockNumber([BigInt(args.assumeProvenThrough)], { account });
|
|
507
|
-
logger.
|
|
519
|
+
logger.warn(`Rollup set to assumedProvenUntil to ${args.assumeProvenThrough}`);
|
|
508
520
|
}
|
|
509
521
|
|
|
510
522
|
// Inbox and Outbox are immutable and are deployed from Rollup's constructor so we just fetch them from the contract.
|
|
511
523
|
const inboxAddress = EthAddress.fromString((await rollup.read.INBOX([])) as any);
|
|
512
|
-
logger.
|
|
524
|
+
logger.verbose(`Inbox available at ${inboxAddress}`);
|
|
513
525
|
|
|
514
526
|
const outboxAddress = EthAddress.fromString((await rollup.read.OUTBOX([])) as any);
|
|
515
|
-
logger.
|
|
527
|
+
logger.verbose(`Outbox available at ${outboxAddress}`);
|
|
516
528
|
|
|
517
529
|
// We need to call a function on the registry to set the various contract addresses.
|
|
518
530
|
const registryContract = getContract({
|
|
@@ -521,6 +533,7 @@ export const deployL1Contracts = async (
|
|
|
521
533
|
client: walletClient,
|
|
522
534
|
});
|
|
523
535
|
if (!(await registryContract.read.isRollupRegistered([getAddress(rollupAddress.toString())]))) {
|
|
536
|
+
logger.info(`Registry ${registryAddress} is not registered to rollup ${rollupAddress}`);
|
|
524
537
|
const upgradeTxHash = await registryContract.write.upgrade([getAddress(rollupAddress.toString())], { account });
|
|
525
538
|
logger.verbose(
|
|
526
539
|
`Upgrading registry contract at ${registryAddress} to rollup ${rollupAddress} in tx ${upgradeTxHash}`,
|
|
@@ -562,6 +575,8 @@ export const deployL1Contracts = async (
|
|
|
562
575
|
governanceAddress,
|
|
563
576
|
};
|
|
564
577
|
|
|
578
|
+
logger.info(`Aztec L1 contracts initialized`, l1Contracts);
|
|
579
|
+
|
|
565
580
|
return {
|
|
566
581
|
walletClient,
|
|
567
582
|
publicClient,
|
|
@@ -576,7 +591,7 @@ class L1Deployer {
|
|
|
576
591
|
private walletClient: WalletClient<HttpTransport, Chain, Account>,
|
|
577
592
|
private publicClient: PublicClient<HttpTransport, Chain>,
|
|
578
593
|
maybeSalt: number | undefined,
|
|
579
|
-
private logger:
|
|
594
|
+
private logger: Logger,
|
|
580
595
|
) {
|
|
581
596
|
this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
|
|
582
597
|
}
|
|
@@ -666,7 +681,7 @@ export async function deployL1Contract(
|
|
|
666
681
|
args: readonly unknown[] = [],
|
|
667
682
|
maybeSalt?: Hex,
|
|
668
683
|
libraries?: Libraries,
|
|
669
|
-
logger?:
|
|
684
|
+
logger?: Logger,
|
|
670
685
|
): Promise<{ address: EthAddress; txHash: Hex | undefined }> {
|
|
671
686
|
let txHash: Hex | undefined = undefined;
|
|
672
687
|
let resultingAddress: Hex | null | undefined = undefined;
|
package/src/eth_cheat_codes.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { toBigIntBE, toHex } from '@aztec/foundation/bigint-buffer';
|
|
2
2
|
import { keccak256 } from '@aztec/foundation/crypto';
|
|
3
3
|
import { type EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
-
import {
|
|
4
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
5
5
|
|
|
6
|
-
import fs from 'fs';
|
|
7
6
|
import { type Hex } from 'viem';
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -18,7 +17,7 @@ export class EthCheatCodes {
|
|
|
18
17
|
/**
|
|
19
18
|
* The logger to use for the eth cheatcodes
|
|
20
19
|
*/
|
|
21
|
-
public logger =
|
|
20
|
+
public logger = createLogger('ethereum:cheat_codes'),
|
|
22
21
|
) {}
|
|
23
22
|
|
|
24
23
|
async rpcCall(method: string, params: any[]) {
|
|
@@ -77,11 +76,15 @@ export class EthCheatCodes {
|
|
|
77
76
|
* @param numberOfBlocks - The number of blocks to mine
|
|
78
77
|
*/
|
|
79
78
|
public async mine(numberOfBlocks = 1): Promise<void> {
|
|
79
|
+
await this.doMine(numberOfBlocks);
|
|
80
|
+
this.logger.verbose(`Mined ${numberOfBlocks} L1 blocks`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private async doMine(numberOfBlocks = 1): Promise<void> {
|
|
80
84
|
const res = await this.rpcCall('hardhat_mine', [numberOfBlocks]);
|
|
81
85
|
if (res.error) {
|
|
82
86
|
throw new Error(`Error mining: ${res.error.message}`);
|
|
83
87
|
}
|
|
84
|
-
this.logger.verbose(`Mined ${numberOfBlocks} L1 blocks`);
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
/**
|
|
@@ -188,37 +191,10 @@ export class EthCheatCodes {
|
|
|
188
191
|
if (res.error) {
|
|
189
192
|
throw new Error(`Error warping: ${res.error.message}`);
|
|
190
193
|
}
|
|
191
|
-
await this.
|
|
194
|
+
await this.doMine();
|
|
192
195
|
this.logger.verbose(`Warped L1 timestamp to ${timestamp}`);
|
|
193
196
|
}
|
|
194
197
|
|
|
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
198
|
/**
|
|
223
199
|
* Load the value at a storage slot of a contract address on eth
|
|
224
200
|
* @param contract - The contract address
|
package/src/l1_tx_utils.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { times } from '@aztec/foundation/collection';
|
|
1
2
|
import {
|
|
2
3
|
type ConfigMappingsType,
|
|
3
4
|
bigintConfigHelper,
|
|
4
5
|
getDefaultConfig,
|
|
5
6
|
numberConfigHelper,
|
|
6
7
|
} from '@aztec/foundation/config';
|
|
7
|
-
import { type
|
|
8
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
8
9
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
9
10
|
import { sleep } from '@aztec/foundation/sleep';
|
|
10
11
|
|
|
@@ -71,6 +72,11 @@ export interface L1TxUtilsConfig {
|
|
|
71
72
|
* How long to wait for a tx to be mined before giving up
|
|
72
73
|
*/
|
|
73
74
|
txTimeoutMs?: number;
|
|
75
|
+
/**
|
|
76
|
+
* How many attempts will be done to get a tx after it was sent?
|
|
77
|
+
* First attempt is done at 1s, second at 2s, third at 3s, etc.
|
|
78
|
+
*/
|
|
79
|
+
txPropagationMaxQueryAttempts?: number;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
export const l1TxUtilsConfigMappings: ConfigMappingsType<L1TxUtilsConfig> = {
|
|
@@ -119,6 +125,11 @@ export const l1TxUtilsConfigMappings: ConfigMappingsType<L1TxUtilsConfig> = {
|
|
|
119
125
|
env: 'L1_TX_MONITOR_TX_TIMEOUT_MS',
|
|
120
126
|
...numberConfigHelper(300_000), // 5 mins
|
|
121
127
|
},
|
|
128
|
+
txPropagationMaxQueryAttempts: {
|
|
129
|
+
description: 'How many attempts will be done to get a tx after it was sent',
|
|
130
|
+
env: 'L1_TX_PROPAGATION_MAX_QUERY_ATTEMPTS',
|
|
131
|
+
...numberConfigHelper(3),
|
|
132
|
+
},
|
|
122
133
|
};
|
|
123
134
|
|
|
124
135
|
export const defaultL1TxUtilsConfig = getDefaultConfig<L1TxUtilsConfig>(l1TxUtilsConfigMappings);
|
|
@@ -129,6 +140,12 @@ export interface L1TxRequest {
|
|
|
129
140
|
value?: bigint;
|
|
130
141
|
}
|
|
131
142
|
|
|
143
|
+
export interface L1BlobInputs {
|
|
144
|
+
blobs: Uint8Array[];
|
|
145
|
+
kzg: any;
|
|
146
|
+
maxFeePerBlobGas: bigint;
|
|
147
|
+
}
|
|
148
|
+
|
|
132
149
|
interface GasPrice {
|
|
133
150
|
maxFeePerGas: bigint;
|
|
134
151
|
maxPriorityFeePerGas: bigint;
|
|
@@ -140,7 +157,7 @@ export class L1TxUtils {
|
|
|
140
157
|
constructor(
|
|
141
158
|
private readonly publicClient: PublicClient,
|
|
142
159
|
private readonly walletClient: WalletClient<HttpTransport, Chain, Account>,
|
|
143
|
-
private readonly logger?:
|
|
160
|
+
private readonly logger?: Logger,
|
|
144
161
|
config?: Partial<L1TxUtilsConfig>,
|
|
145
162
|
) {
|
|
146
163
|
this.config = {
|
|
@@ -158,6 +175,7 @@ export class L1TxUtils {
|
|
|
158
175
|
public async sendTransaction(
|
|
159
176
|
request: L1TxRequest,
|
|
160
177
|
_gasConfig?: Partial<L1TxUtilsConfig> & { fixedGas?: bigint },
|
|
178
|
+
_blobInputs?: L1BlobInputs,
|
|
161
179
|
): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
|
|
162
180
|
const gasConfig = { ...this.config, ..._gasConfig };
|
|
163
181
|
const account = this.walletClient.account;
|
|
@@ -171,16 +189,20 @@ export class L1TxUtils {
|
|
|
171
189
|
|
|
172
190
|
const gasPrice = await this.getGasPrice(gasConfig);
|
|
173
191
|
|
|
192
|
+
const blobInputs = _blobInputs || {};
|
|
174
193
|
const txHash = await this.walletClient.sendTransaction({
|
|
175
194
|
...request,
|
|
195
|
+
...blobInputs,
|
|
176
196
|
gas: gasLimit,
|
|
177
197
|
maxFeePerGas: gasPrice.maxFeePerGas,
|
|
178
198
|
maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
|
|
179
199
|
});
|
|
180
200
|
|
|
181
|
-
this.logger?.verbose(
|
|
182
|
-
|
|
183
|
-
|
|
201
|
+
this.logger?.verbose(`Sent L1 transaction ${txHash}`, {
|
|
202
|
+
gasLimit,
|
|
203
|
+
maxFeePerGas: formatGwei(gasPrice.maxFeePerGas),
|
|
204
|
+
maxPriorityFeePerGas: formatGwei(gasPrice.maxPriorityFeePerGas),
|
|
205
|
+
});
|
|
184
206
|
|
|
185
207
|
return { txHash, gasLimit, gasPrice };
|
|
186
208
|
}
|
|
@@ -197,15 +219,19 @@ export class L1TxUtils {
|
|
|
197
219
|
initialTxHash: Hex,
|
|
198
220
|
params: { gasLimit: bigint },
|
|
199
221
|
_gasConfig?: Partial<L1TxUtilsConfig>,
|
|
222
|
+
_blobInputs?: L1BlobInputs,
|
|
200
223
|
): Promise<TransactionReceipt> {
|
|
201
224
|
const gasConfig = { ...this.config, ..._gasConfig };
|
|
202
225
|
const account = this.walletClient.account;
|
|
226
|
+
const blobInputs = _blobInputs || {};
|
|
227
|
+
const makeGetTransactionBackoff = () =>
|
|
228
|
+
makeBackoff(times(gasConfig.txPropagationMaxQueryAttempts ?? 3, i => i + 1));
|
|
203
229
|
|
|
204
230
|
// Retry a few times, in case the tx is not yet propagated.
|
|
205
231
|
const tx = await retry<GetTransactionReturnType>(
|
|
206
232
|
() => this.publicClient.getTransaction({ hash: initialTxHash }),
|
|
207
233
|
`Getting L1 transaction ${initialTxHash}`,
|
|
208
|
-
|
|
234
|
+
makeGetTransactionBackoff(),
|
|
209
235
|
this.logger,
|
|
210
236
|
true,
|
|
211
237
|
);
|
|
@@ -230,9 +256,9 @@ export class L1TxUtils {
|
|
|
230
256
|
try {
|
|
231
257
|
const receipt = await this.publicClient.getTransactionReceipt({ hash });
|
|
232
258
|
if (receipt) {
|
|
233
|
-
this.logger?.debug(`L1
|
|
259
|
+
this.logger?.debug(`L1 transaction ${hash} mined`);
|
|
234
260
|
if (receipt.status === 'reverted') {
|
|
235
|
-
this.logger?.error(`L1
|
|
261
|
+
this.logger?.error(`L1 transaction ${hash} reverted`);
|
|
236
262
|
}
|
|
237
263
|
return receipt;
|
|
238
264
|
}
|
|
@@ -248,14 +274,14 @@ export class L1TxUtils {
|
|
|
248
274
|
const tx = await retry<GetTransactionReturnType>(
|
|
249
275
|
() => this.publicClient.getTransaction({ hash: currentTxHash }),
|
|
250
276
|
`Getting L1 transaction ${currentTxHash}`,
|
|
251
|
-
|
|
277
|
+
makeGetTransactionBackoff(),
|
|
252
278
|
this.logger,
|
|
253
279
|
true,
|
|
254
280
|
);
|
|
255
281
|
const timePassed = Date.now() - lastAttemptSent;
|
|
256
282
|
|
|
257
283
|
if (tx && timePassed < gasConfig.stallTimeMs!) {
|
|
258
|
-
this.logger?.debug(`L1
|
|
284
|
+
this.logger?.debug(`L1 transaction ${currentTxHash} pending. Time passed: ${timePassed}ms.`);
|
|
259
285
|
|
|
260
286
|
// Check timeout before continuing
|
|
261
287
|
if (gasConfig.txTimeoutMs) {
|
|
@@ -280,12 +306,13 @@ export class L1TxUtils {
|
|
|
280
306
|
);
|
|
281
307
|
|
|
282
308
|
this.logger?.debug(
|
|
283
|
-
`L1
|
|
309
|
+
`L1 transaction ${currentTxHash} appears stuck. Attempting speed-up ${attempts}/${gasConfig.maxAttempts} ` +
|
|
284
310
|
`with new priority fee ${formatGwei(newGasPrice.maxPriorityFeePerGas)} gwei`,
|
|
285
311
|
);
|
|
286
312
|
|
|
287
313
|
currentTxHash = await this.walletClient.sendTransaction({
|
|
288
314
|
...request,
|
|
315
|
+
...blobInputs,
|
|
289
316
|
nonce,
|
|
290
317
|
gas: params.gasLimit,
|
|
291
318
|
maxFeePerGas: newGasPrice.maxFeePerGas,
|
|
@@ -308,7 +335,7 @@ export class L1TxUtils {
|
|
|
308
335
|
txTimedOut = Date.now() - initialTxTime > gasConfig.txTimeoutMs!;
|
|
309
336
|
}
|
|
310
337
|
}
|
|
311
|
-
throw new Error(`L1
|
|
338
|
+
throw new Error(`L1 transaction ${currentTxHash} timed out`);
|
|
312
339
|
}
|
|
313
340
|
|
|
314
341
|
/**
|
|
@@ -320,9 +347,10 @@ export class L1TxUtils {
|
|
|
320
347
|
public async sendAndMonitorTransaction(
|
|
321
348
|
request: L1TxRequest,
|
|
322
349
|
gasConfig?: Partial<L1TxUtilsConfig> & { fixedGas?: bigint },
|
|
350
|
+
blobInputs?: L1BlobInputs,
|
|
323
351
|
): Promise<TransactionReceipt> {
|
|
324
|
-
const { txHash, gasLimit } = await this.sendTransaction(request, gasConfig);
|
|
325
|
-
return this.monitorTransaction(request, txHash, { gasLimit }, gasConfig);
|
|
352
|
+
const { txHash, gasLimit } = await this.sendTransaction(request, gasConfig, blobInputs);
|
|
353
|
+
return this.monitorTransaction(request, txHash, { gasLimit }, gasConfig, blobInputs);
|
|
326
354
|
}
|
|
327
355
|
|
|
328
356
|
/**
|
|
@@ -377,10 +405,12 @@ export class L1TxUtils {
|
|
|
377
405
|
// Ensure priority fee doesn't exceed max fee
|
|
378
406
|
const maxPriorityFeePerGas = priorityFee > maxFeePerGas ? maxFeePerGas : priorityFee;
|
|
379
407
|
|
|
380
|
-
this.logger?.debug(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
408
|
+
this.logger?.debug(`Computed gas price`, {
|
|
409
|
+
attempt,
|
|
410
|
+
baseFee: formatGwei(baseFee),
|
|
411
|
+
maxFeePerGas: formatGwei(maxFeePerGas),
|
|
412
|
+
maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
|
|
413
|
+
});
|
|
384
414
|
|
|
385
415
|
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
386
416
|
}
|
|
@@ -388,9 +418,23 @@ export class L1TxUtils {
|
|
|
388
418
|
/**
|
|
389
419
|
* Estimates gas and adds buffer
|
|
390
420
|
*/
|
|
391
|
-
public async estimateGas(
|
|
421
|
+
public async estimateGas(
|
|
422
|
+
account: Account,
|
|
423
|
+
request: L1TxRequest,
|
|
424
|
+
_gasConfig?: L1TxUtilsConfig,
|
|
425
|
+
_blobInputs?: L1BlobInputs,
|
|
426
|
+
): Promise<bigint> {
|
|
392
427
|
const gasConfig = { ...this.config, ..._gasConfig };
|
|
393
|
-
|
|
428
|
+
let initialEstimate = 0n;
|
|
429
|
+
// Viem does not allow blobs to be sent via public client's estimate gas, so any estimation will fail.
|
|
430
|
+
// Strangely, the only way to get gas and send blobs is prepareTransactionRequest().
|
|
431
|
+
// See: https://github.com/wevm/viem/issues/2075
|
|
432
|
+
if (_blobInputs) {
|
|
433
|
+
initialEstimate = (await this.walletClient.prepareTransactionRequest({ account, ...request, ..._blobInputs }))
|
|
434
|
+
.gas;
|
|
435
|
+
} else {
|
|
436
|
+
initialEstimate = await this.publicClient.estimateGas({ account, ...request });
|
|
437
|
+
}
|
|
394
438
|
|
|
395
439
|
// Add buffer based on either fixed amount or percentage
|
|
396
440
|
const withBuffer = initialEstimate + (initialEstimate * (gasConfig.gasLimitBufferPercentage ?? 0n)) / 100n;
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
const res = await this.rpcCall('hardhat_dumpState', []);
|
|
16
|
+
if (res.error) {
|
|
17
|
+
throw new Error(`Error dumping state: ${res.error.message}`);
|
|
18
|
+
}
|
|
19
|
+
const jsonContent = JSON.stringify(res.result);
|
|
20
|
+
fs.writeFileSync(`${fileName}.json`, jsonContent, 'utf8');
|
|
21
|
+
this.logger.verbose(`Dumped state to ${fileName}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Loads the chain state from a file.
|
|
26
|
+
* @param fileName - The file name to load state from
|
|
27
|
+
*/
|
|
28
|
+
public async loadChainState(fileName: string): Promise<void> {
|
|
29
|
+
const data = JSON.parse(fs.readFileSync(`${fileName}.json`, 'utf8'));
|
|
30
|
+
const res = await this.rpcCall('hardhat_loadState', [data]);
|
|
31
|
+
if (res.error) {
|
|
32
|
+
throw new Error(`Error loading state: ${res.error.message}`);
|
|
33
|
+
}
|
|
34
|
+
this.logger.verbose(`Loaded state from ${fileName}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/test/index.ts
CHANGED
package/src/test/tx_delayer.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { omit } from '@aztec/foundation/collection';
|
|
2
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
3
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
3
4
|
|
|
4
5
|
import { inspect } from 'util';
|
|
@@ -8,11 +9,12 @@ import {
|
|
|
8
9
|
type PublicClient,
|
|
9
10
|
type WalletClient,
|
|
10
11
|
keccak256,
|
|
12
|
+
parseTransaction,
|
|
11
13
|
publicActions,
|
|
12
14
|
walletActions,
|
|
13
15
|
} from 'viem';
|
|
14
16
|
|
|
15
|
-
export function waitUntilBlock<T extends Client>(client: T, blockNumber: number | bigint, logger?:
|
|
17
|
+
export function waitUntilBlock<T extends Client>(client: T, blockNumber: number | bigint, logger?: Logger) {
|
|
16
18
|
const publicClient =
|
|
17
19
|
'getBlockNumber' in client && typeof client.getBlockNumber === 'function'
|
|
18
20
|
? (client as unknown as PublicClient)
|
|
@@ -30,7 +32,7 @@ export function waitUntilBlock<T extends Client>(client: T, blockNumber: number
|
|
|
30
32
|
);
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
export function waitUntilL1Timestamp<T extends Client>(client: T, timestamp: number | bigint, logger?:
|
|
35
|
+
export function waitUntilL1Timestamp<T extends Client>(client: T, timestamp: number | bigint, logger?: Logger) {
|
|
34
36
|
const publicClient =
|
|
35
37
|
'getBlockNumber' in client && typeof client.getBlockNumber === 'function'
|
|
36
38
|
? (client as unknown as PublicClient)
|
|
@@ -89,12 +91,13 @@ class DelayerImpl implements Delayer {
|
|
|
89
91
|
/**
|
|
90
92
|
* Returns a new client (without modifying the one passed in) with an injected tx delayer.
|
|
91
93
|
* The delayer can be used to hold off the next tx to be sent until a given block number.
|
|
94
|
+
* TODO(#10824): This doesn't play along well with blob txs for some reason.
|
|
92
95
|
*/
|
|
93
96
|
export function withDelayer<T extends WalletClient>(
|
|
94
97
|
client: T,
|
|
95
98
|
opts: { ethereumSlotDuration: bigint | number },
|
|
96
99
|
): { client: T; delayer: Delayer } {
|
|
97
|
-
const logger =
|
|
100
|
+
const logger = createLogger('ethereum:tx_delayer');
|
|
98
101
|
const delayer = new DelayerImpl(opts);
|
|
99
102
|
const extended = client
|
|
100
103
|
// Tweak sendRawTransaction so it uses the delay defined in the delayer.
|
|
@@ -116,16 +119,25 @@ export function withDelayer<T extends WalletClient>(
|
|
|
116
119
|
// Compute the tx hash manually so we emulate sendRawTransaction response
|
|
117
120
|
const { serializedTransaction } = args[0];
|
|
118
121
|
const txHash = keccak256(serializedTransaction);
|
|
119
|
-
logger.info(`Delaying tx ${txHash} until ${inspect(waitUntil)}
|
|
122
|
+
logger.info(`Delaying tx ${txHash} until ${inspect(waitUntil)}`, {
|
|
123
|
+
argsLen: args.length,
|
|
124
|
+
...omit(parseTransaction(serializedTransaction), 'data', 'sidecars'),
|
|
125
|
+
});
|
|
120
126
|
|
|
121
127
|
// Do not await here so we can return the tx hash immediately as if it had been sent on the spot.
|
|
122
128
|
// Instead, delay it so it lands on the desired block number or timestamp, assuming anvil will
|
|
123
129
|
// mine it immediately.
|
|
124
130
|
void wait
|
|
125
131
|
.then(async () => {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
const clientTxHash = await client.sendRawTransaction(...args);
|
|
133
|
+
if (clientTxHash !== txHash) {
|
|
134
|
+
logger.error(`Tx hash returned by the client does not match computed one`, {
|
|
135
|
+
clientTxHash,
|
|
136
|
+
computedTxHash: txHash,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
logger.info(`Sent previously delayed tx ${clientTxHash} to land on ${inspect(waitUntil)}`);
|
|
140
|
+
delayer.txs.push(clientTxHash);
|
|
129
141
|
})
|
|
130
142
|
.catch(err => logger.error(`Error sending tx after delay`, err));
|
|
131
143
|
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Fr } from '@aztec/foundation/fields';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
type Abi,
|
|
@@ -27,7 +27,7 @@ export function extractEvent<
|
|
|
27
27
|
abi: TAbi,
|
|
28
28
|
eventName: TEventName,
|
|
29
29
|
filter?: (log: TEventType) => boolean,
|
|
30
|
-
logger?:
|
|
30
|
+
logger?: Logger,
|
|
31
31
|
): TEventType {
|
|
32
32
|
const event = tryExtractEvent(logs, address, abi, eventName, filter, logger);
|
|
33
33
|
if (!event) {
|
|
@@ -46,7 +46,7 @@ function tryExtractEvent<
|
|
|
46
46
|
abi: TAbi,
|
|
47
47
|
eventName: TEventName,
|
|
48
48
|
filter?: (log: TEventType) => boolean,
|
|
49
|
-
logger?:
|
|
49
|
+
logger?: Logger,
|
|
50
50
|
): TEventType | undefined {
|
|
51
51
|
for (const log of logs) {
|
|
52
52
|
if (log.address.toLowerCase() === address.toLowerCase()) {
|