diff --git a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts index 1125a528ec27..b061eec00294 100644 --- a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts +++ b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts @@ -1,6 +1,8 @@ -import { type PXE, type Tx, type TxExecutionRequest } from '@aztec/circuit-types'; +import { type Tx, type TxExecutionRequest } from '@aztec/circuit-types'; +import { GasSettings } from '@aztec/circuits.js'; -import { type FeeOptions } from '../entrypoint/entrypoint.js'; +import { type Wallet } from '../account/wallet.js'; +import { type ExecutionRequestInit, type FeeOptions } from '../entrypoint/entrypoint.js'; import { SentTx } from './sent_tx.js'; /** @@ -8,15 +10,12 @@ import { SentTx } from './sent_tx.js'; * Allows the user to specify the sender address and nonce for a transaction. */ export type SendMethodOptions = { - /** - * Wether to skip the simulation of the public part of the transaction. - */ + /** Wether to skip the simulation of the public part of the transaction. */ skipPublicSimulation?: boolean; - - /** - * The fee options for the transaction. - */ + /** The fee options for the transaction. */ fee?: FeeOptions; + /** Whether to run an initial simulation of the tx with high gas limit to figure out actual gas settings (will default to true later down the road). */ + estimateGas?: boolean; }; /** @@ -27,7 +26,7 @@ export abstract class BaseContractInteraction { protected tx?: Tx; protected txRequest?: TxExecutionRequest; - constructor(protected pxe: PXE) {} + constructor(protected wallet: Wallet) {} /** * Create a transaction execution request ready to be simulated. @@ -43,7 +42,7 @@ export abstract class BaseContractInteraction { */ public async prove(options: SendMethodOptions = {}): Promise { const txRequest = this.txRequest ?? (await this.create(options)); - this.tx = await this.pxe.proveTx(txRequest, !options.skipPublicSimulation); + this.tx = await this.wallet.proveTx(txRequest, !options.skipPublicSimulation); return this.tx; } @@ -59,9 +58,37 @@ export abstract class BaseContractInteraction { public send(options: SendMethodOptions = {}) { const promise = (async () => { const tx = this.tx ?? (await this.prove(options)); - return this.pxe.sendTx(tx); + return this.wallet.sendTx(tx); })(); - return new SentTx(this.pxe, promise); + return new SentTx(this.wallet, promise); + } + + /** + * Estimates gas for a given tx request and returns defaults gas settings for it. + * @param txRequest - Transaction execution request to process. + * @returns Gas settings. + */ + protected async estimateGas(txRequest: TxExecutionRequest) { + const simulationResult = await this.wallet.simulateTx(txRequest, true); + const { totalGas: gasLimits, teardownGas: teardownGasLimits } = simulationResult.getGasLimits(); + return GasSettings.default({ gasLimits, teardownGasLimits }); + } + + /** + * Helper method to return fee options based on the user opts, estimating tx gas if needed. + * @param request - Request to execute for this interaction. + * @returns Fee options for the actual transaction. + */ + protected async getFeeOptions(request: ExecutionRequestInit) { + const fee = request.fee; + if (fee) { + const txRequest = await this.wallet.createTxExecutionRequest(request); + const simulationResult = await this.wallet.simulateTx(txRequest, true); + const { totalGas: gasLimits, teardownGas: teardownGasLimits } = simulationResult.getGasLimits(); + const gasSettings = GasSettings.default({ ...fee.gasSettings, gasLimits, teardownGasLimits }); + return { ...fee, gasSettings }; + } + return fee; } } diff --git a/yarn-project/aztec.js/src/contract/batch_call.ts b/yarn-project/aztec.js/src/contract/batch_call.ts index 79cead71f4d8..ac7a65f40a14 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.ts @@ -5,7 +5,7 @@ import { BaseContractInteraction, type SendMethodOptions } from './base_contract /** A batch of function calls to be sent as a single transaction through a wallet. */ export class BatchCall extends BaseContractInteraction { - constructor(protected wallet: Wallet, protected calls: FunctionCall[]) { + constructor(wallet: Wallet, protected calls: FunctionCall[]) { super(wallet); } @@ -17,10 +17,9 @@ export class BatchCall extends BaseContractInteraction { */ public async create(opts?: SendMethodOptions): Promise { if (!this.txRequest) { - this.txRequest = await this.wallet.createTxExecutionRequest({ - calls: this.calls, - fee: opts?.fee, - }); + const calls = this.calls; + const fee = opts?.estimateGas ? await this.getFeeOptions({ calls, fee: opts?.fee }) : opts?.fee; + this.txRequest = await this.wallet.createTxExecutionRequest({ calls, fee }); } return this.txRequest; } diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index 6d194bfa9f1a..393445868919 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -25,7 +25,7 @@ export type SimulateMethodOptions = { */ export class ContractFunctionInteraction extends BaseContractInteraction { constructor( - protected wallet: Wallet, + wallet: Wallet, protected contractAddress: AztecAddress, protected functionDao: FunctionAbi, protected args: any[], @@ -47,10 +47,9 @@ export class ContractFunctionInteraction extends BaseContractInteraction { throw new Error("Can't call `create` on an unconstrained function."); } if (!this.txRequest) { - this.txRequest = await this.wallet.createTxExecutionRequest({ - calls: [this.request()], - fee: opts?.fee, - }); + const calls = [this.request()]; + const fee = opts?.estimateGas ? await this.getFeeOptions({ calls, fee: opts?.fee }) : opts?.fee; + this.txRequest = await this.wallet.createTxExecutionRequest({ calls, fee }); } return this.txRequest; } @@ -98,12 +97,12 @@ export class ContractFunctionInteraction extends BaseContractInteraction { argsOfCalls: [packedArgs], authWitnesses: [], }); - const simulatedTx = await this.pxe.simulateTx(txRequest, true, options.from ?? this.wallet.getAddress()); + const simulatedTx = await this.wallet.simulateTx(txRequest, true, options.from ?? this.wallet.getAddress()); const flattened = simulatedTx.privateReturnValues; return flattened ? decodeReturnValues(this.functionDao, flattened) : []; } else { const txRequest = await this.create(); - const simulatedTx = await this.pxe.simulateTx(txRequest, true); + const simulatedTx = await this.wallet.simulateTx(txRequest, true); this.txRequest = undefined; const flattened = simulatedTx.publicOutput?.publicReturnValues; return flattened ? decodeReturnValues(this.functionDao, flattened) : []; diff --git a/yarn-project/aztec.js/src/contract/deploy_method.ts b/yarn-project/aztec.js/src/contract/deploy_method.ts index 40fd67bbcf43..5521901f9437 100644 --- a/yarn-project/aztec.js/src/contract/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_method.ts @@ -57,7 +57,7 @@ export class DeployMethod extends Bas constructor( private publicKeysHash: Fr, - protected wallet: Wallet, + wallet: Wallet, private artifact: ContractArtifact, private postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise, private args: any[] = [], @@ -80,11 +80,14 @@ export class DeployMethod extends Bas if (!this.txRequest) { this.txRequest = await this.wallet.createTxExecutionRequest(await this.request(options)); // TODO: Should we add the contracts to the DB here, or once the tx has been sent or mined? - await this.pxe.registerContract({ artifact: this.artifact, instance: this.instance! }); + await this.wallet.registerContract({ artifact: this.artifact, instance: this.instance! }); } return this.txRequest; } + // REFACTOR: Having a `request` method with different semantics than the ones in the other + // derived ContractInteractions is confusing. We should unify the flow of all ContractInteractions. + /** * Returns an array of function calls that represent this operation. Useful as a building * block for constructing batch requests. @@ -102,13 +105,20 @@ export class DeployMethod extends Bas throw new Error(`No function calls needed to deploy contract ${this.artifact.name}`); } - this.functionCalls = { + const request = { calls: [...deployment.calls, ...bootstrap.calls], authWitnesses: [...(deployment.authWitnesses ?? []), ...(bootstrap.authWitnesses ?? [])], packedArguments: [...(deployment.packedArguments ?? []), ...(bootstrap.packedArguments ?? [])], fee: options.fee, }; + + if (options.estimateGas) { + request.fee = await this.getFeeOptions(request); + } + + this.functionCalls = request; } + return this.functionCalls; } @@ -134,7 +144,7 @@ export class DeployMethod extends Bas // Register the contract class if it hasn't been published already. if (!options.skipClassRegistration) { - if (await this.pxe.isContractClassPubliclyRegistered(contractClass.id)) { + if (await this.wallet.isContractClassPubliclyRegistered(contractClass.id)) { this.log.debug( `Skipping registration of already registered contract class ${contractClass.id.toString()} for ${instance.address.toString()}`, ); @@ -192,7 +202,7 @@ export class DeployMethod extends Bas this.log.debug( `Sent deployment tx of ${this.artifact.name} contract with deployment address ${instance.address.toString()}`, ); - return new DeploySentTx(this.pxe, txHashPromise, this.postDeployCtor, instance); + return new DeploySentTx(this.wallet, txHashPromise, this.postDeployCtor, instance); } /** diff --git a/yarn-project/circuits.js/src/structs/gas_settings.ts b/yarn-project/circuits.js/src/structs/gas_settings.ts index 3a0878bff5d6..e777c9467d3e 100644 --- a/yarn-project/circuits.js/src/structs/gas_settings.ts +++ b/yarn-project/circuits.js/src/structs/gas_settings.ts @@ -62,13 +62,14 @@ export class GasSettings { } /** Default gas settings to use when user has not provided them. */ - static default() { - return new GasSettings( - new Gas(DEFAULT_GAS_LIMIT, DEFAULT_GAS_LIMIT), - new Gas(DEFAULT_TEARDOWN_GAS_LIMIT, DEFAULT_TEARDOWN_GAS_LIMIT), - new GasFees(new Fr(DEFAULT_MAX_FEE_PER_GAS), new Fr(DEFAULT_MAX_FEE_PER_GAS)), - new Fr(DEFAULT_INCLUSION_FEE), - ); + static default(overrides?: Partial>) { + return GasSettings.from({ + gasLimits: { l2Gas: DEFAULT_GAS_LIMIT, daGas: DEFAULT_GAS_LIMIT }, + teardownGasLimits: { l2Gas: DEFAULT_TEARDOWN_GAS_LIMIT, daGas: DEFAULT_TEARDOWN_GAS_LIMIT }, + maxFeesPerGas: { feePerL2Gas: new Fr(DEFAULT_MAX_FEE_PER_GAS), feePerDaGas: new Fr(DEFAULT_MAX_FEE_PER_GAS) }, + inclusionFee: new Fr(DEFAULT_INCLUSION_FEE), + ...overrides, + }); } /** Gas settings to use for simulating a contract call. */