diff --git a/boxes/boxes/vanilla/app/main.ts b/boxes/boxes/vanilla/app/main.ts index ddce11af435c..4c35c1045b97 100644 --- a/boxes/boxes/vanilla/app/main.ts +++ b/boxes/boxes/vanilla/app/main.ts @@ -198,7 +198,7 @@ async function updateVoteTally(wallet: Wallet, from: AztecAddress) { ) ); - const batchResult = await new BatchCall(wallet, payloads).simulate({ from }); + const { result: batchResult } = await new BatchCall(wallet, payloads).simulate({ from }); batchResult.forEach(({ result: value }, i) => { results[i + 1] = value; diff --git a/yarn-project/aztec.js/src/contract/batch_call.test.ts b/yarn-project/aztec.js/src/contract/batch_call.test.ts index 88b6e4427d60..cecd701b27ec 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.test.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.test.ts @@ -146,7 +146,7 @@ describe('BatchCall', () => { { name: 'simulateTx', result: txSimResult }, ] as any); - const results = await batchCall.simulate({ from: await AztecAddress.random() }); + const { result: results } = await batchCall.simulate({ from: await AztecAddress.random() }); // Verify wallet.batch was called once with both utility calls AND simulateTx expect(wallet.batch).toHaveBeenCalledTimes(1); @@ -212,7 +212,7 @@ describe('BatchCall', () => { { name: 'executeUtility', result: utilityResult2 }, ] as any); - const results = await batchCall.simulate({ from: await AztecAddress.random() }); + const { result: results } = await batchCall.simulate({ from: await AztecAddress.random() }); expect(wallet.batch).toHaveBeenCalledTimes(1); expect(wallet.batch).toHaveBeenCalledWith([ @@ -247,7 +247,7 @@ describe('BatchCall', () => { const utilityResult = UtilityExecutionResult.random(); wallet.batch.mockResolvedValue([{ name: 'executeUtility', result: utilityResult }] as any); - const results = await batchCall.simulate({ from: await AztecAddress.random() }); + const { result: results } = await batchCall.simulate({ from: await AztecAddress.random() }); expect(results).toHaveLength(1); expect(results[0].offchainEffects).toEqual([]); @@ -307,7 +307,7 @@ describe('BatchCall', () => { { name: 'simulateTx', result: txSimResult }, ] as any); - const results = await batchCall.simulate({ from: await AztecAddress.random() }); + const { result: results } = await batchCall.simulate({ from: await AztecAddress.random() }); expect(results).toHaveLength(3); expect(results[0].offchainMessages).toEqual([ @@ -349,7 +349,7 @@ describe('BatchCall', () => { wallet.batch.mockResolvedValue([{ name: 'simulateTx', result: txSimResult }] as any); - const results = await batchCall.simulate({ from: await AztecAddress.random() }); + const { result: results } = await batchCall.simulate({ from: await AztecAddress.random() }); expect(wallet.batch).toHaveBeenCalledTimes(1); expect(wallet.batch).toHaveBeenCalledWith([ @@ -376,7 +376,7 @@ describe('BatchCall', () => { it('should handle empty batch', async () => { batchCall = new BatchCall(wallet, []); - const results = await batchCall.simulate({ from: await AztecAddress.random() }); + const { result: results } = await batchCall.simulate({ from: await AztecAddress.random() }); expect(wallet.batch).not.toHaveBeenCalled(); expect(results).toEqual([]); diff --git a/yarn-project/aztec.js/src/contract/batch_call.ts b/yarn-project/aztec.js/src/contract/batch_call.ts index 3012c2dd8f01..ecf21515d616 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.ts @@ -3,9 +3,11 @@ import { ExecutionPayload, TxSimulationResult, UtilityExecutionResult, mergeExec import type { BatchedMethod, Wallet } from '../wallet/wallet.js'; import { BaseContractInteraction } from './base_contract_interaction.js'; +import { getGasLimits } from './get_gas_limits.js'; import { type RequestInteractionOptions, type SimulateInteractionOptions, + type SimulationResult, extractOffchainOutput, toSimulateOptions, } from './interaction_options.js'; @@ -45,7 +47,7 @@ export class BatchCall extends BaseContractInteraction { * @param options - An optional object containing additional configuration for the interaction. * @returns The results of all the interactions that make up the batch */ - public async simulate(options: SimulateInteractionOptions): Promise { + public async simulate(options: SimulateInteractionOptions): Promise { const { indexedExecutionPayloads, utility } = (await this.getExecutionPayloads()).reduce<{ /** Keep track of the number of private calls to retrieve the return values */ privateIndex: 0; @@ -119,10 +121,11 @@ export class BatchCall extends BaseContractInteraction { } // Process tx simulation result (it comes last if present) + let simulatedTx: TxSimulationResult | undefined; if (indexedExecutionPayloads.length > 0) { const txResultWrapper = batchResults[utility.length]; if (txResultWrapper.name === 'simulateTx') { - const simulatedTx = txResultWrapper.result as TxSimulationResult; + simulatedTx = txResultWrapper.result as TxSimulationResult; indexedExecutionPayloads.forEach(([request, callIndex, resultIndex]) => { const call = request.calls[0]; // As account entrypoints are private, for private functions we retrieve the return values from the first nested call @@ -130,21 +133,34 @@ export class BatchCall extends BaseContractInteraction { // For public functions we retrieve the first values directly from the public output. const rawReturnValues = call.type == FunctionType.PRIVATE - ? simulatedTx.getPrivateReturnValues()?.nested?.[resultIndex].values - : simulatedTx.getPublicReturnValues()?.[resultIndex].values; + ? simulatedTx!.getPrivateReturnValues()?.nested?.[resultIndex].values + : simulatedTx!.getPublicReturnValues()?.[resultIndex].values; results[callIndex] = { result: rawReturnValues ? decodeFromAbi(call.returnTypes, rawReturnValues) : [], ...extractOffchainOutput( - simulatedTx.offchainEffects, - simulatedTx.publicInputs.constants.anchorBlockHeader.globalVariables.timestamp, + simulatedTx!.offchainEffects, + simulatedTx!.publicInputs.constants.anchorBlockHeader.globalVariables.timestamp, ), }; }); } } - return results; + if ((options.includeMetadata || options.fee?.estimateGas) && simulatedTx) { + const { gasLimits, teardownGasLimits } = getGasLimits(simulatedTx, options.fee?.estimatedGasPadding); + this.log.verbose( + `Estimated gas limits for batch tx: DA=${gasLimits.daGas} L2=${gasLimits.l2Gas} teardownDA=${teardownGasLimits.daGas} teardownL2=${teardownGasLimits.l2Gas}`, + ); + return { + result: results, + estimatedGas: { gasLimits, teardownGasLimits }, + offchainEffects: [], + offchainMessages: [], + }; + } + + return { result: results, offchainEffects: [], offchainMessages: [] }; } protected async getExecutionPayloads(): Promise { diff --git a/yarn-project/bot/src/factory.ts b/yarn-project/bot/src/factory.ts index 99995c4bf8a6..c62e3b78a85b 100644 --- a/yarn-project/bot/src/factory.ts +++ b/yarn-project/bot/src/factory.ts @@ -27,7 +27,7 @@ import { PrivateTokenContract } from '@aztec/noir-contracts.js/PrivateToken'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { TestContract } from '@aztec/noir-test-contracts.js/Test'; import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract'; -import { GasSettings } from '@aztec/stdlib/gas'; +import { GasFees, GasSettings } from '@aztec/stdlib/gas'; import type { AztecNode, AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; @@ -223,7 +223,12 @@ export class BotFactory { const paymentMethod = new FeeJuicePaymentMethodWithClaim(accountManager.address, claim); const deployMethod = await accountManager.getDeployMethod(); const maxFeesPerGas = (await this.aztecNode.getCurrentMinFees()).mul(1 + this.config.minFeePadding); - const gasSettings = GasSettings.default({ maxFeesPerGas }); + + const { estimatedGas } = await deployMethod.simulate({ + from: AztecAddress.ZERO, + fee: { estimateGas: true, paymentMethod }, + }); + const gasSettings = GasSettings.from({ ...estimatedGas!, maxFeesPerGas, maxPriorityFeesPerGas: GasFees.empty() }); await this.withNoMinTxsPerBlock(async () => { const { txHash } = await deployMethod.send({ @@ -231,7 +236,7 @@ export class BotFactory { fee: { gasSettings, paymentMethod }, wait: NO_WAIT, }); - this.log.info(`Sent tx for account deployment with hash ${txHash.toString()}`); + this.log.info(`Sent tx for account deployment with hash ${txHash.toString()}`, { gasSettings }); return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); }); this.log.info(`Account deployed at ${address}`); @@ -297,8 +302,9 @@ export class BotFactory { await deploy.register(); } else { this.log.info(`Deploying token contract at ${address.toString()}`); - const { txHash } = await deploy.send({ ...deployOpts, wait: NO_WAIT }); - this.log.info(`Sent tx for token setup with hash ${txHash.toString()}`); + const { estimatedGas } = await deploy.simulate({ ...deployOpts, fee: { estimateGas: true } }); + const { txHash } = await deploy.send({ ...deployOpts, fee: { gasSettings: estimatedGas }, wait: NO_WAIT }); + this.log.info(`Sent tx for token setup with hash ${txHash.toString()}`, { estimatedGas }); await this.withNoMinTxsPerBlock(async () => { await waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); return token; @@ -338,10 +344,19 @@ export class BotFactory { const amm = AMMContract.at(instance.address, this.wallet); this.log.info(`AMM deployed at ${amm.address}`); - const { receipt: minterReceipt } = await lpToken.methods - .set_minter(amm.address, true) - .send({ from: deployer, wait: { timeout: this.config.txMinedWaitSeconds } }); - this.log.info(`Set LP token minter to AMM txHash=${minterReceipt.txHash.toString()}`); + const setMinterInteraction = lpToken.methods.set_minter(amm.address, true); + const { estimatedGas: setMinterGas } = await setMinterInteraction.simulate({ + from: deployer, + fee: { estimateGas: true }, + }); + const { receipt: minterReceipt } = await setMinterInteraction.send({ + from: deployer, + fee: { gasSettings: setMinterGas }, + wait: { timeout: this.config.txMinedWaitSeconds }, + }); + this.log.info(`Set LP token minter to AMM txHash=${minterReceipt.txHash.toString()}`, { + estimatedGas: setMinterGas, + }); this.log.info(`Liquidity token initialized`); return amm; @@ -409,22 +424,44 @@ export class BotFactory { .getFunctionCall(), }); - const { receipt: mintReceipt } = await new BatchCall(this.wallet, [ + const mintBatch = new BatchCall(this.wallet, [ token0.methods.mint_to_private(liquidityProvider, MINT_BALANCE), token1.methods.mint_to_private(liquidityProvider, MINT_BALANCE), - ]).send({ from: liquidityProvider, wait: { timeout: this.config.txMinedWaitSeconds } }); + ]); + const { estimatedGas: mintGas } = await mintBatch.simulate({ + from: liquidityProvider, + fee: { estimateGas: true }, + }); + const { receipt: mintReceipt } = await mintBatch.send({ + from: liquidityProvider, + fee: { gasSettings: mintGas }, + wait: { timeout: this.config.txMinedWaitSeconds }, + }); - this.log.info(`Sent mint tx: ${mintReceipt.txHash.toString()}`); + this.log.info(`Sent mint tx: ${mintReceipt.txHash.toString()}`, { estimatedGas: mintGas }); - const { receipt: addLiquidityReceipt } = await amm.methods - .add_liquidity(amount0Max, amount1Max, amount0Min, amount1Min, authwitNonce) - .send({ - from: liquidityProvider, - authWitnesses: [token0Authwit, token1Authwit], - wait: { timeout: this.config.txMinedWaitSeconds }, - }); + const addLiquidityInteraction = amm.methods.add_liquidity( + amount0Max, + amount1Max, + amount0Min, + amount1Min, + authwitNonce, + ); + const { estimatedGas: addLiquidityGas } = await addLiquidityInteraction.simulate({ + from: liquidityProvider, + fee: { estimateGas: true }, + authWitnesses: [token0Authwit, token1Authwit], + }); + const { receipt: addLiquidityReceipt } = await addLiquidityInteraction.send({ + from: liquidityProvider, + fee: { gasSettings: addLiquidityGas }, + authWitnesses: [token0Authwit, token1Authwit], + wait: { timeout: this.config.txMinedWaitSeconds }, + }); - this.log.info(`Sent tx to add liquidity to the AMM: ${addLiquidityReceipt.txHash.toString()}`); + this.log.info(`Sent tx to add liquidity to the AMM: ${addLiquidityReceipt.txHash.toString()}`, { + estimatedGas: addLiquidityGas, + }); this.log.info(`Liquidity added`); const [newT0Bal, newT1Bal, newLPBal] = await getPrivateBalances(); @@ -445,9 +482,10 @@ export class BotFactory { this.log.info(`Contract ${name} at ${address.toString()} already deployed`); await deploy.register(); } else { - this.log.info(`Deploying contract ${name} at ${address.toString()}`); + const { estimatedGas } = await deploy.simulate({ ...deployOpts, fee: { estimateGas: true } }); + this.log.info(`Deploying contract ${name} at ${address.toString()}`, { estimatedGas }); await this.withNoMinTxsPerBlock(async () => { - const { txHash } = await deploy.send({ ...deployOpts, wait: NO_WAIT }); + const { txHash } = await deploy.send({ ...deployOpts, fee: { gasSettings: estimatedGas }, wait: NO_WAIT }); this.log.info(`Sent contract ${name} setup tx with hash ${txHash.toString()}`); return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); }); @@ -491,13 +529,16 @@ export class BotFactory { // PrivateToken's mint accesses contract-level private storage vars (admin, total_supply). const additionalScopes = isStandardToken ? undefined : [token.address]; + const mintBatch = new BatchCall(token.wallet, calls); + const { estimatedGas } = await mintBatch.simulate({ from: minter, fee: { estimateGas: true }, additionalScopes }); await this.withNoMinTxsPerBlock(async () => { - const { txHash } = await new BatchCall(token.wallet, calls).send({ + const { txHash } = await mintBatch.send({ from: minter, additionalScopes, + fee: { gasSettings: estimatedGas }, wait: NO_WAIT, }); - this.log.info(`Sent token mint tx with hash ${txHash.toString()}`); + this.log.info(`Sent token mint tx with hash ${txHash.toString()}`, { estimatedGas }); return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); }); } diff --git a/yarn-project/end-to-end/src/e2e_state_vars.test.ts b/yarn-project/end-to-end/src/e2e_state_vars.test.ts index 6d2f58a7f37d..29435110942e 100644 --- a/yarn-project/end-to-end/src/e2e_state_vars.test.ts +++ b/yarn-project/end-to-end/src/e2e_state_vars.test.ts @@ -68,7 +68,7 @@ describe('e2e_state_vars', () => { contract.methods.get_public_immutable_constrained_private_indirect(), contract.methods.get_public_immutable(), ]).simulate({ from: defaultAccountAddress }) - ).map((r: any) => r.result); + ).result.map((r: any) => r.result); expect(a).toEqual(c); expect(b).toEqual({ account: c.account, value: c.value + 1n }); @@ -87,7 +87,7 @@ describe('e2e_state_vars', () => { contract.methods.get_public_immutable_constrained_public_indirect(), contract.methods.get_public_immutable(), ]).simulate({ from: defaultAccountAddress }) - ).map((r: any) => r.result); + ).result.map((r: any) => r.result); expect(a).toEqual(c); expect(b).toEqual({ account: c.account, value: c.value + 1n }); diff --git a/yarn-project/end-to-end/src/simulators/token_simulator.ts b/yarn-project/end-to-end/src/simulators/token_simulator.ts index a2065beb4426..a10bc0c7de17 100644 --- a/yarn-project/end-to-end/src/simulators/token_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/token_simulator.ts @@ -110,7 +110,7 @@ export class TokenSimulator { chunk(calls, 5).map(batch => new BatchCall(this.defaultWallet, batch).simulate({ from: this.defaultAddress })), ) ) - .flat() + .flatMap(r => r.result) .map(r => r.result); expect(results[0]).toEqual(this.totalSupply);