Skip to content

Commit 2fd66d3

Browse files
mverzilliaztec-bot
authored andcommitted
fix: tweak depositToAztec gas config (#24607)
While trying to deploy the latest nightly standard contracts to testnet I experienced gas estimation issues. According to Claude these issues are due to variability in Merkle tree insertions: there's around 40% of gas cost spread between the happiest case (no tree "climbing" needed) and the worst case (10 level climb). If eth_estimateGas occurs during the happiest case, and actual TX execution during the worst case, the default 20% buffer we use in Aztec.js turns out not to be sufficient. Closes F-794 (cherry picked from commit 2734da1)
1 parent 5acba2d commit 2fd66d3

1 file changed

Lines changed: 73 additions & 26 deletions

File tree

yarn-project/aztec.js/src/ethereum/portal_manager.ts

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import {
2+
type L1TxConfig,
3+
type L1TxUtils,
4+
createL1TxUtils,
5+
getL1TxUtilsConfigEnvVars,
6+
} from '@aztec/ethereum/l1-tx-utils';
17
import type { ExtendedViemWalletClient, ViemContract } from '@aztec/ethereum/types';
28
import { extractEvent } from '@aztec/ethereum/utils';
39
import type { EpochNumber } from '@aztec/foundation/branded-types';
@@ -16,7 +22,7 @@ import { computeL2ToL1MessageHash, computeSecretHash } from '@aztec/stdlib/hash'
1622
import type { AztecNode } from '@aztec/stdlib/interfaces/client';
1723
import { getL2ToL1MessageLeafId } from '@aztec/stdlib/messaging';
1824

19-
import { type Hex, getContract, toFunctionSelector } from 'viem';
25+
import { type Hex, encodeFunctionData, getContract, toFunctionSelector } from 'viem';
2026

2127
/** L1 to L2 message info to claim it on L2. */
2228
export type L2Claim = {
@@ -51,10 +57,26 @@ export async function generateClaimSecret(logger?: Logger): Promise<[Fr, Fr]> {
5157
return [secret, secretHash];
5258
}
5359

60+
// `Inbox.sendL2Message` (reached by every `depositToAztec*` call) inserts into a height-10
61+
// (`L1_TO_L2_MSG_SUBTREE_HEIGHT`) incremental frontier tree. The per-insert cost swings by up to ~10 hash levels
62+
// (which translates to ~40k gas) depending on where the global message index lands when the tx is mined: inserts that
63+
// complete a subtree cascade through cold SLOADs + an SSTORE vs cheap inserts that touch one slot. A point-in-time
64+
// `eth_estimateGas` taken at a cheap index therefore under-sizes a deposit that mines at a subtree boundary. That ~40k
65+
// swing exceeds the default 20% buffer on a ~100k deposit. That is why we raise the gas-limit buffer to 2x here.
66+
// Note: a larger operator override via L1_GAS_LIMIT_BUFFER_PERCENTAGE still wins.
67+
const INBOX_DEPOSIT_GAS_LIMIT_BUFFER_PERCENTAGE = 100;
68+
69+
/** Per-call gas config for `depositToAztec*`: the Inbox-deposit buffer floor, or a larger operator override. */
70+
function inboxDepositGasConfig(): L1TxConfig {
71+
const configuredBuffer = getL1TxUtilsConfigEnvVars().gasLimitBufferPercentage ?? 0;
72+
return { gasLimitBufferPercentage: Math.max(configuredBuffer, INBOX_DEPOSIT_GAS_LIMIT_BUFFER_PERCENTAGE) };
73+
}
74+
5475
/** Helper for managing an ERC20 on L1. */
5576
export class L1TokenManager {
5677
private contract: ViemContract<typeof TestERC20Abi>;
5778
private handler: ViemContract<typeof FeeAssetHandlerAbi> | undefined;
79+
private readonly l1TxUtils: L1TxUtils;
5880

5981
public constructor(
6082
/** Address of the ERC20 contract. */
@@ -76,6 +98,7 @@ export class L1TokenManager {
7698
client: this.extendedClient,
7799
});
78100
}
101+
this.l1TxUtils = createL1TxUtils(this.extendedClient, { logger }, getL1TxUtilsConfigEnvVars());
79102
}
80103

81104
/** Returns the amount of tokens available to mint via the handler.
@@ -108,8 +131,10 @@ export class L1TokenManager {
108131
const mintAmount = await this.getMintAmount();
109132
this.logger.info(`Minting ${mintAmount} tokens for ${stringifyEthAddress(address, addressName)}`);
110133
// NOTE: the handler mints a fixed amount.
111-
await this.extendedClient.waitForTransactionReceipt({
112-
hash: await this.handler.write.mint([address]),
134+
await this.l1TxUtils.sendAndMonitorTransaction({
135+
to: this.handler.address,
136+
abi: FeeAssetHandlerAbi,
137+
data: encodeFunctionData({ abi: FeeAssetHandlerAbi, functionName: 'mint', args: [address] }),
113138
});
114139
}
115140

@@ -121,8 +146,10 @@ export class L1TokenManager {
121146
*/
122147
public async approve(amount: bigint, address: Hex, addressName = '') {
123148
this.logger.info(`Approving ${amount} tokens for ${stringifyEthAddress(address, addressName)}`);
124-
await this.extendedClient.waitForTransactionReceipt({
125-
hash: await this.contract.write.approve([address, amount]),
149+
await this.l1TxUtils.sendAndMonitorTransaction({
150+
to: this.contract.address,
151+
abi: TestERC20Abi,
152+
data: encodeFunctionData({ abi: TestERC20Abi, functionName: 'approve', args: [address, amount] }),
126153
});
127154
}
128155
}
@@ -131,6 +158,7 @@ export class L1TokenManager {
131158
export class L1FeeJuicePortalManager {
132159
private readonly tokenManager: L1TokenManager;
133160
private readonly contract: ViemContract<typeof FeeJuicePortalAbi>;
161+
private readonly l1TxUtils: L1TxUtils;
134162

135163
constructor(
136164
portalAddress: EthAddress,
@@ -145,6 +173,7 @@ export class L1FeeJuicePortalManager {
145173
abi: FeeJuicePortalAbi,
146174
client: extendedClient,
147175
});
176+
this.l1TxUtils = createL1TxUtils(extendedClient, { logger }, getL1TxUtilsConfigEnvVars());
148177
}
149178

150179
/** Returns the associated token manager for the L1 ERC20. */
@@ -176,9 +205,14 @@ export class L1FeeJuicePortalManager {
176205

177206
await this.contract.simulate.depositToAztecPublic(args);
178207

179-
const txReceipt = await this.extendedClient.waitForTransactionReceipt({
180-
hash: await this.contract.write.depositToAztecPublic(args),
181-
});
208+
const { receipt: txReceipt } = await this.l1TxUtils.sendAndMonitorTransaction(
209+
{
210+
to: this.contract.address,
211+
abi: FeeJuicePortalAbi,
212+
data: encodeFunctionData({ abi: FeeJuicePortalAbi, functionName: 'depositToAztecPublic', args }),
213+
},
214+
inboxDepositGasConfig(),
215+
);
182216

183217
this.logger.info('Deposited to Aztec public successfully', { txReceipt });
184218

@@ -249,6 +283,7 @@ export class L1FeeJuicePortalManager {
249283
export class L1ToL2TokenPortalManager {
250284
protected readonly portal: ViemContract<typeof TokenPortalAbi>;
251285
protected readonly tokenManager: L1TokenManager;
286+
protected readonly l1TxUtils: L1TxUtils;
252287

253288
constructor(
254289
portalAddress: EthAddress,
@@ -263,6 +298,7 @@ export class L1ToL2TokenPortalManager {
263298
abi: TokenPortalAbi,
264299
client: extendedClient,
265300
});
301+
this.l1TxUtils = createL1TxUtils(extendedClient, { logger }, getL1TxUtilsConfigEnvVars());
266302
}
267303

268304
/** Returns the token manager for the underlying L1 token. */
@@ -280,15 +316,17 @@ export class L1ToL2TokenPortalManager {
280316
const [claimSecret, claimSecretHash] = await this.bridgeSetup(amount, mint);
281317

282318
this.logger.info('Sending L1 tokens to L2 to be claimed publicly');
283-
const { request } = await this.portal.simulate.depositToAztecPublic([
284-
to.toString(),
285-
amount,
286-
claimSecretHash.toString(),
287-
]);
288-
289-
const txReceipt = await this.extendedClient.waitForTransactionReceipt({
290-
hash: await this.extendedClient.writeContract(request),
291-
});
319+
const args = [to.toString(), amount, claimSecretHash.toString()] as const;
320+
await this.portal.simulate.depositToAztecPublic(args);
321+
322+
const { receipt: txReceipt } = await this.l1TxUtils.sendAndMonitorTransaction(
323+
{
324+
to: this.portal.address,
325+
abi: TokenPortalAbi,
326+
data: encodeFunctionData({ abi: TokenPortalAbi, functionName: 'depositToAztecPublic', args }),
327+
},
328+
inboxDepositGasConfig(),
329+
);
292330

293331
const log = extractEvent(
294332
txReceipt.logs,
@@ -334,11 +372,17 @@ export class L1ToL2TokenPortalManager {
334372
const [claimSecret, claimSecretHash] = await this.bridgeSetup(amount, mint);
335373

336374
this.logger.info('Sending L1 tokens to L2 to be claimed privately');
337-
const { request } = await this.portal.simulate.depositToAztecPrivate([amount, claimSecretHash.toString()]);
338-
339-
const txReceipt = await this.extendedClient.waitForTransactionReceipt({
340-
hash: await this.extendedClient.writeContract(request),
341-
});
375+
const args = [amount, claimSecretHash.toString()] as const;
376+
await this.portal.simulate.depositToAztecPrivate(args);
377+
378+
const { receipt: txReceipt } = await this.l1TxUtils.sendAndMonitorTransaction(
379+
{
380+
to: this.portal.address,
381+
abi: TokenPortalAbi,
382+
data: encodeFunctionData({ abi: TokenPortalAbi, functionName: 'depositToAztecPrivate', args }),
383+
},
384+
inboxDepositGasConfig(),
385+
);
342386

343387
const log = extractEvent(
344388
txReceipt.logs,
@@ -437,18 +481,21 @@ export class L1TokenPortalManager extends L1ToL2TokenPortalManager {
437481
}
438482

439483
// Call function on L1 contract to consume the message
440-
const { request: withdrawRequest } = await this.portal.simulate.withdraw([
484+
const withdrawArgs = [
441485
recipient.toString(),
442486
amount,
443487
false,
444488
BigInt(epochNumber),
445489
BigInt(numCheckpointsInEpoch),
446490
messageIndex,
447491
siblingPath.toBufferArray().map((buf: Buffer): Hex => `0x${buf.toString('hex')}`),
448-
]);
492+
] as const;
493+
await this.portal.simulate.withdraw(withdrawArgs);
449494

450-
await this.extendedClient.waitForTransactionReceipt({
451-
hash: await this.extendedClient.writeContract(withdrawRequest),
495+
await this.l1TxUtils.sendAndMonitorTransaction({
496+
to: this.portal.address,
497+
abi: TokenPortalAbi,
498+
data: encodeFunctionData({ abi: TokenPortalAbi, functionName: 'withdraw', args: withdrawArgs }),
452499
});
453500

454501
const isConsumedAfter = await this.outbox.read.hasMessageBeenConsumedAtEpoch([BigInt(epochNumber), messageLeafId]);

0 commit comments

Comments
 (0)