Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion yarn-project/aztec.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"./tx_hash": "./dest/api/tx_hash.js",
"./wallet": "./dest/api/wallet.js",
"./utils": "./dest/api/utils.js",
"./testing": "./dest/api/testing.js"
"./testing": "./dest/api/testing.js",
"./fee/testing": "./dest/api/fee_testing.js"
},
"typedocOptions": {
"entryPoints": [
Expand Down
84 changes: 70 additions & 14 deletions yarn-project/aztec.js/src/account_manager/account_manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { DefaultMultiCallEntrypoint } from '@aztec/entrypoints/multicall';
import { Fr } from '@aztec/foundation/fields';
import { CompleteAddress, type ContractInstanceWithAddress } from '@aztec/stdlib/contract';
import { getContractInstanceFromDeployParams } from '@aztec/stdlib/contract';
import type { GasSettings } from '@aztec/stdlib/gas';
import type { PXE } from '@aztec/stdlib/interfaces/client';
import { deriveKeys } from '@aztec/stdlib/keys';

Expand All @@ -10,9 +12,9 @@ import type { AccountInterface } from '../account/interface.js';
import { Contract } from '../contract/contract.js';
import { DeployMethod, type DeployOptions } from '../contract/deploy_method.js';
import { DefaultWaitOpts, type WaitOpts } from '../contract/sent_tx.js';
import { DefaultMultiCallEntrypoint } from '../entrypoint/default_multi_call_entrypoint.js';
import { AccountEntrypointMetaPaymentMethod } from '../fee/account_entrypoint_meta_payment_method.js';
import { FeeJuicePaymentMethod, type FeePaymentMethod } from '../index.js';
import { AccountWalletWithSecretKey, SignerlessWallet, type Wallet } from '../wallet/index.js';
import { DeployAccountMethod } from './deploy_account_method.js';
import { DeployAccountSentTx } from './deploy_account_sent_tx.js';

/**
Expand Down Expand Up @@ -139,12 +141,11 @@ export class AccountManager {

/**
* Returns the pre-populated deployment method to deploy the account contract that backs this account.
* Typically you will not need this method and can call `deploy` directly. Use this for having finer
* grained control on when to create, simulate, and send the deployment tx.
* If no wallet is provided, it uses a signerless wallet with the multi call entrypoint
* @param deployWallet - Wallet used for deploying the account contract.
* @returns A DeployMethod instance that deploys this account contract.
* @returns A DeployMethod instance that deploys this account contract
*/
public async getDeployMethod(deployWallet?: Wallet) {
async #getDeployMethod(deployWallet?: Wallet): Promise<DeployMethod> {
const artifact = await this.accountContract.getContractArtifact();

if (!(await this.isDeployable())) {
Expand Down Expand Up @@ -179,14 +180,33 @@ export class AccountManager {
// and it can't be used unless the contract is initialized.
const wallet = new SignerlessWallet(this.pxe, new DefaultMultiCallEntrypoint(chainId, protocolVersion));

return new DeployAccountMethod(
this.accountContract.getAuthWitnessProvider(completeAddress),
return new DeployMethod(
this.getPublicKeys(),
wallet,
artifact,
address => Contract.at(address, artifact, wallet),
constructorArgs,
constructorName,
);
}

/**
* Returns a FeePaymentMethod that routes the original one provided as an argument
* through the account's entrypoint. This allows an account contract to pay
* for its own deployment and initialization.
* @param originalPaymentMethod - originalPaymentMethod The original payment method to be wrapped.
* @returns A FeePaymentMethod that routes the original one through the account's entrypoint (AccountEntrypointMetaPaymentMethod)
*/
async #getSelfPaymentMethod(originalPaymentMethod?: FeePaymentMethod) {
const artifact = await this.accountContract.getContractArtifact();
const wallet = await this.getWallet();
const address = wallet.getAddress();
return new AccountEntrypointMetaPaymentMethod(
artifact,
wallet,
'entrypoint',
address,
originalPaymentMethod ?? new FeeJuicePaymentMethod(address),
);
}

Expand All @@ -199,21 +219,57 @@ export class AccountManager {
* @returns A SentTx object that can be waited to get the associated Wallet.
*/
public deploy(opts?: DeployAccountOptions): DeployAccountSentTx {
const sentTx = this.getDeployMethod(opts?.deployWallet)
.then(deployMethod =>
deployMethod.send({
let deployMethod: DeployMethod;
const sentTx = this.#getDeployMethod(opts?.deployWallet)
.then(method => {
deployMethod = method;
if (!opts?.deployWallet && opts?.fee) {
return this.#getSelfPaymentMethod(opts?.fee?.paymentMethod);
}
})
.then(maybeWrappedPaymentMethod => {
let fee = opts?.fee;
if (maybeWrappedPaymentMethod) {
fee = { ...opts?.fee, paymentMethod: maybeWrappedPaymentMethod };
}
return deployMethod.send({
contractAddressSalt: new Fr(this.salt),
skipClassRegistration: opts?.skipClassRegistration ?? true,
skipPublicDeployment: opts?.skipPublicDeployment ?? true,
skipInitialization: opts?.skipInitialization ?? false,
universalDeploy: true,
fee: opts?.fee,
}),
)
fee,
});
})
.then(tx => tx.getTxHash());
return new DeployAccountSentTx(this.pxe, sentTx, this.getWallet());
}

/**
* Estimates the gas needed to deploy the account contract that backs this account.
* This method is here to ensure that the fee payment method is correctly set up in case
* the account contract needs to pay for its own deployment.
* @param opts - Fee options to be used for the deployment.
* @returns The gas estimations for the account contract deployment and initialization.
*/
public async estimateDeploymentGas(
opts?: DeployAccountOptions,
): Promise<Pick<GasSettings, 'gasLimits' | 'teardownGasLimits'>> {
const deployMethod = await this.#getDeployMethod(opts?.deployWallet);
const fee =
!opts?.deployWallet && opts?.fee
? { ...opts.fee, paymentMethod: await this.#getSelfPaymentMethod(opts.fee.paymentMethod) }
: opts?.fee;
return deployMethod.estimateGas({
contractAddressSalt: new Fr(this.salt),
skipClassRegistration: opts?.skipClassRegistration ?? true,
skipPublicDeployment: opts?.skipPublicDeployment ?? true,
skipInitialization: opts?.skipInitialization ?? false,
universalDeploy: true,
fee,
});
}

/**
* Deploys the account contract that backs this account if needed and awaits the tx to be mined.
* Uses the salt provided in the constructor or a randomly generated one. If no initialization
Expand Down
87 changes: 0 additions & 87 deletions yarn-project/aztec.js/src/account_manager/deploy_account_method.ts

This file was deleted.

1 change: 0 additions & 1 deletion yarn-project/aztec.js/src/account_manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { AccountManager, type DeployAccountOptions } from './account_manager.js';
export { DeployAccountMethod } from './deploy_account_method.js';
export { type DeployAccountTxReceipt, DeployAccountSentTx } from './deploy_account_sent_tx.js';
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/api/fee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { FeeJuicePaymentMethod } from '../fee/fee_juice_payment_method.js';
export { PrivateFeePaymentMethod } from '../fee/private_fee_payment_method.js';
export { PublicFeePaymentMethod } from '../fee/public_fee_payment_method.js';
export { FeeJuicePaymentMethodWithClaim } from '../fee/fee_juice_payment_method_with_claim.js';
export { SponsoredFeePaymentMethod } from '../fee/sponsored_fee_payment.js';
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/api/fee_testing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SponsoredFeePaymentMethod } from '../fee/sponsored_fee_payment.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
EncodedAppEntrypointCalls,
EncodedCallsForEntrypoint,
computeCombinedPayloadHash,
} from '@aztec/entrypoints/encoding';
import type { AuthWitnessProvider, FeePaymentMethod } from '@aztec/entrypoints/interfaces';
import { ExecutionPayload } from '@aztec/entrypoints/payload';
import {
type ContractArtifact,
type FunctionArtifact,
FunctionCall,
FunctionSelector,
encodeArguments,
getFunctionArtifactByName,
} from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import type { GasSettings } from '@aztec/stdlib/gas';

/**
* Fee payment method that allows a contract to pay for its own deployment
* It works by rerouting the provided fee payment method through the account's entrypoint,
* which sets itself as fee payer.
*
* Usually, in order to pay fees it is necessary to obtain an ExecutionPayload that encodes the necessary information
* That is sent to the user's account entrypoint, that has plumbing to handle a fee payload.
* If there's no account contract yet (it's being deployed) a MultiCallContract is used, which doesn't have a concept of fees or
* how to handle this payload.
* HOWEVER,the account contract entrypoint does, so this method reshapes that fee payload into a call to the account contract entrypoint
* being deployed with the original fee payload.
*
* This class can be seen in action in AccountManager.ts#getSelfPaymentMethod
*/
export class AccountEntrypointMetaPaymentMethod implements FeePaymentMethod {
constructor(
private artifact: ContractArtifact,
private authWitnessProvider: AuthWitnessProvider,
private feePaymentNameOrArtifact: string | FunctionArtifact,
private accountAddress: AztecAddress,
private paymentMethod: FeePaymentMethod,
) {}

getAsset(): Promise<AztecAddress> {
return this.paymentMethod.getAsset();
}

async getExecutionPayload(gasSettings: GasSettings): Promise<ExecutionPayload> {
const emptyAppCalls = await EncodedAppEntrypointCalls.fromAppExecution([]);
// Get the execution payload for the fee, it includes the calls and potentially authWitnesses
const { calls: feeCalls, authWitnesses: feeAuthwitnesses } = await this.paymentMethod.getExecutionPayload(
gasSettings,
);
// Encode the calls for the fee
const feePayer = await this.paymentMethod.getFeePayer(gasSettings);
const isFeePayer = feePayer.equals(this.accountAddress);
const feeEncodedCalls = await EncodedCallsForEntrypoint.fromFeeCalls(feeCalls, isFeePayer);

// Get the entrypoint args
const args = [emptyAppCalls, feeEncodedCalls, false];
const feePaymentArtifact =
typeof this.feePaymentNameOrArtifact === 'string'
? getFunctionArtifactByName(this.artifact, this.feePaymentNameOrArtifact)
: this.feePaymentNameOrArtifact;

const entrypointCall = new FunctionCall(
feePaymentArtifact.name,
this.accountAddress,
await FunctionSelector.fromNameAndParameters(feePaymentArtifact.name, feePaymentArtifact.parameters),
feePaymentArtifact.functionType,
feePaymentArtifact.isStatic,
encodeArguments(feePaymentArtifact, args),
feePaymentArtifact.returnTypes,
);

// Compute the authwitness required to verify the combined payload
const combinedPayloadAuthWitness = await this.authWitnessProvider.createAuthWit(
await computeCombinedPayloadHash(emptyAppCalls, feeEncodedCalls),
);

return new ExecutionPayload(
[entrypointCall],
[combinedPayloadAuthWitness, ...feeAuthwitnesses],
[],
[...emptyAppCalls.hashedArguments, ...feeEncodedCalls.hashedArguments],
);
}

getFeePayer(gasSettings: GasSettings): Promise<AztecAddress> {
return this.paymentMethod.getFeePayer(gasSettings);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { ExecutionPayload } from '@aztec/entrypoints/payload';
import { FunctionSelector, FunctionType } from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';

/**
* A fee payment method that uses a contract that blindly sponsors transactions.
* This contract is expected to be prefunded in testing environments.
*/
export class SponsoredFeePaymentMethod implements FeePaymentMethod {
constructor(private paymentContract: AztecAddress) {}

Expand Down
1 change: 0 additions & 1 deletion yarn-project/aztec/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ export {
getDeployedBananaCoinAddress,
getDeployedBananaFPCAddress,
getDeployedSponsoredFPCAddress,
SponsoredFeePaymentMethod,
} from './sandbox/index.js';
1 change: 0 additions & 1 deletion yarn-project/aztec/src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from './sandbox.js';

export { getDeployedBananaCoinAddress, getDeployedBananaFPCAddress } from './banana_fpc.js';
export { getDeployedSponsoredFPCAddress } from './sponsored_fpc.js';
export { SponsoredFeePaymentMethod } from './sponsored_fee_payment_method.js';
6 changes: 3 additions & 3 deletions yarn-project/aztec/src/sandbox/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { deployFundedSchnorrAccounts, getInitialTestAccounts } from '@aztec/acco
import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
import { AnvilTestWatcher, EthCheatCodes } from '@aztec/aztec.js/testing';
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
import { setupCanonicalL2FeeJuice } from '@aztec/cli/setup-contracts';
import { setupCanonicalL2FeeJuice, setupSponsoredFPC } from '@aztec/cli/cli-utils';
import { GENESIS_ARCHIVE_ROOT, GENESIS_BLOCK_HASH } from '@aztec/constants';
import {
NULL_KEY,
Expand Down Expand Up @@ -34,7 +34,7 @@ import { foundry } from 'viem/chains';
import { createAccountLogs } from '../cli/util.js';
import { DefaultMnemonic } from '../mnemonic.js';
import { getBananaFPCAddress, setupBananaFPC } from './banana_fpc.js';
import { getSponsoredFPCAddress, setupSponsoredFPC } from './sponsored_fpc.js';
import { getSponsoredFPCAddress } from './sponsored_fpc.js';

const logger = createLogger('sandbox');

Expand Down Expand Up @@ -181,7 +181,7 @@ export async function createSandbox(config: Partial<SandboxConfig> = {}, userLog

const deployer = await getSchnorrWallet(pxe, initialAccounts[0].address, initialAccounts[0].signingKey);
await setupBananaFPC(initialAccounts, deployer, userLog);
await setupSponsoredFPC(deployer, userLog);
await setupSponsoredFPC(pxe, userLog);
}

const stop = async () => {
Expand Down
Loading
Loading