Skip to content

Commit

Permalink
feat: Use gas estimation in aztecjs
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed May 7, 2024
1 parent 7e9af33 commit 09258ca
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 37 deletions.
53 changes: 40 additions & 13 deletions yarn-project/aztec.js/src/contract/base_contract_interaction.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
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';

/**
* Represents options for calling a (constrained) function in a contract.
* 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;
};

/**
Expand All @@ -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.
Expand All @@ -43,7 +42,7 @@ export abstract class BaseContractInteraction {
*/
public async prove(options: SendMethodOptions = {}): Promise<Tx> {
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;
}

Expand All @@ -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;
}
}
9 changes: 4 additions & 5 deletions yarn-project/aztec.js/src/contract/batch_call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -17,10 +17,9 @@ export class BatchCall extends BaseContractInteraction {
*/
public async create(opts?: SendMethodOptions): Promise<TxExecutionRequest> {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[],
Expand All @@ -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;
}
Expand Down Expand Up @@ -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) : [];
Expand Down
20 changes: 15 additions & 5 deletions yarn-project/aztec.js/src/contract/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class DeployMethod<TContract extends ContractBase = Contract> extends Bas

constructor(
private publicKeysHash: Fr,
protected wallet: Wallet,
wallet: Wallet,
private artifact: ContractArtifact,
private postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise<TContract>,
private args: any[] = [],
Expand All @@ -80,11 +80,14 @@ export class DeployMethod<TContract extends ContractBase = Contract> 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.
Expand All @@ -102,13 +105,20 @@ export class DeployMethod<TContract extends ContractBase = Contract> 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;
}

Expand All @@ -134,7 +144,7 @@ export class DeployMethod<TContract extends ContractBase = Contract> 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()}`,
);
Expand Down Expand Up @@ -192,7 +202,7 @@ export class DeployMethod<TContract extends ContractBase = Contract> 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);
}

/**
Expand Down
15 changes: 8 additions & 7 deletions yarn-project/circuits.js/src/structs/gas_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FieldsOf<GasSettings>>) {
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. */
Expand Down

0 comments on commit 09258ca

Please sign in to comment.