Skip to content

Commit

Permalink
refactor: deploy_account_method
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr committed Apr 3, 2024
1 parent 8361dd6 commit 51ed6a1
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 59 deletions.
40 changes: 21 additions & 19 deletions yarn-project/accounts/src/testing/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generatePublicKey } from '@aztec/aztec.js';
import { type DeploySentTx, generatePublicKey } from '@aztec/aztec.js';
import { type AccountWalletWithPrivateKey } from '@aztec/aztec.js/wallet';
import { type PXE } from '@aztec/circuit-types';
import { Fr, GrumpkinScalar } from '@aztec/foundation/fields';
Expand Down Expand Up @@ -58,24 +58,26 @@ export async function deployInitialTestAccounts(pxe: PXE) {
privateKey,
};
});
// Attempt to get as much parallelism as possible
const deployMethods = await Promise.all(
accounts.map(async x => {
const deployMethod = await x.account.getDeployMethod();
await deployMethod.create({
contractAddressSalt: x.account.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
universalDeploy: true,
});
await deployMethod.prove({});
return deployMethod;
}),
);
// Send tx together to try and get them in the same rollup
const sentTxs = deployMethods.map(dm => {
return dm.send();
});

const sentTxs: DeploySentTx[] = [];
for (const { account } of accounts) {
const deploymentMethod = await account.getDeployMethod();

// pxe needs to prove txs one-by-one
// this is because the tx use capsules and the capsule stack is a shared resource
// TODO #5556 parallelize this back
await deploymentMethod.prove({
contractAddressSalt: account.salt,
});

// the txs can be processed in parallel by the sequencer though
sentTxs.push(
deploymentMethod.send({
contractAddressSalt: account.salt,
}),
);
}

await Promise.all(
sentTxs.map(async (tx, i) => {
const wallet = await accounts[i].account.getWallet();
Expand Down
3 changes: 0 additions & 3 deletions yarn-project/accounts/src/testing/create_account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ export async function createAccounts(pxe: PXE, numberOfAccounts = 1): Promise<Ac
await account.getDeployMethod().then(d =>
d.prove({
contractAddressSalt: account.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
universalDeploy: true,
}),
);
accounts.push(account);
Expand Down
15 changes: 3 additions & 12 deletions yarn-project/aztec.js/src/account_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import { type AccountContract } from '../account/contract.js';
import { type Salt } from '../account/index.js';
import { type AccountInterface } from '../account/interface.js';
import { DeployAccountMethod } from '../contract/deploy_account_method.js';
import { type DeployMethod } from '../contract/deploy_method.js';
import { DefaultWaitOpts, type WaitOpts } from '../contract/sent_tx.js';
import { type FeeOptions } from '../entrypoint/entrypoint.js';
import { Contract } from '../index.js';
import { waitForAccountSynch } from '../utils/account.js';
import { generatePublicKey } from '../utils/index.js';
import { AccountWalletWithPrivateKey, SignerlessWallet } from '../wallet/index.js';
import { AccountWalletWithPrivateKey } from '../wallet/index.js';
import { DeployAccountSentTx } from './deploy_account_sent_tx.js';

/**
Expand All @@ -28,7 +26,7 @@ export class AccountManager {
private completeAddress?: CompleteAddress;
private instance?: ContractInstanceWithAddress;
private encryptionPublicKey?: PublicKey;
private deployMethod?: DeployMethod;
private deployMethod?: DeployAccountMethod;

constructor(
private pxe: PXE,
Expand Down Expand Up @@ -133,16 +131,12 @@ export class AccountManager {
// We use a signerless wallet so we hit the account contract directly and it deploys itself.
// If we used getWallet, the deployment would get routed via the account contract entrypoint
// instead of directly hitting the initializer.
const deployWallet = new SignerlessWallet(this.pxe);
const args = this.accountContract.getDeploymentArgs() ?? [];
this.deployMethod = new DeployAccountMethod(
this.pxe,
await this.getAccount(),
encryptionPublicKey,
deployWallet,
this.accountContract.getContractArtifact(),
(address, wallet) => {
return Contract.at(address, this.accountContract.getContractArtifact(), wallet);
},
args,
);
}
Expand All @@ -162,9 +156,6 @@ export class AccountManager {
const wallet = await this.getWallet();
const sentTx = deployMethod.send({
contractAddressSalt: this.salt,
skipClassRegistration: true,
skipPublicDeployment: true,
universalDeploy: true,
fee,
});
return new DeployAccountSentTx(wallet, sentTx.getTxHash());
Expand Down
119 changes: 95 additions & 24 deletions yarn-project/aztec.js/src/contract/deploy_account_method.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,136 @@
import { PackedArguments, TxExecutionRequest } from '@aztec/circuit-types';
import { type AztecAddress, type PublicKey, TxContext } from '@aztec/circuits.js';
import { type FunctionCall, type PXE, PackedArguments, type Tx, TxExecutionRequest } from '@aztec/circuit-types';
import {
AztecAddress,
type EthAddress,
type Fr,
FunctionData,
type PublicKey,
TxContext,
getContractInstanceFromDeployParams,
} from '@aztec/circuits.js';
import { type FunctionArtifact, getInitializer } from '@aztec/foundation/abi';

import { type AuthWitnessProvider } from '../account/interface.js';
import { type Wallet } from '../account/wallet.js';
import { type ContractArtifact, type FunctionArtifact } from '../api/abi.js';
import { type ContractArtifact } from '../api/abi.js';
import { EntrypointPayload } from '../entrypoint/payload.js';
import { type Contract } from './contract.js';
import { type ContractInstanceWithAddress } from '../index.js';
import { BaseContractInteraction, type SendMethodOptions } from './base_contract_interaction.js';
import { Contract } from './contract.js';
import { type ContractBase } from './contract_base.js';
import { DeployMethod, type DeployOptions } from './deploy_method.js';
import { DeploySentTx } from './deploy_sent_tx.js';

/**
* Options to pass to account contract initialization.
*/
type DeployAccountOptions = {
/** The Ethereum address of the Portal contract. */
portalContract?: EthAddress;
/** An optional salt value used to deterministically calculate the contract address. */
contractAddressSalt?: Fr;
} & SendMethodOptions;

/**
* Contract interaction for deployment. Handles class registration, public instance deployment,
* and initialization of the contract. Extends the BaseContractInteraction class.
*/
export class DeployAccountMethod<TContract extends ContractBase = Contract> extends DeployMethod<TContract> {
export class DeployAccountMethod<TContract extends ContractBase = Contract> extends BaseContractInteraction {
private initializerArtifact?: FunctionArtifact;
private instance?: ContractInstanceWithAddress;

constructor(
pxe: PXE,
private authWitnessProvider: AuthWitnessProvider,
publicKey: PublicKey,
wallet: Wallet,
artifact: ContractArtifact,
postDeployCtor: (address: AztecAddress, wallet: Wallet) => Promise<TContract>,
args: any[] = [],
constructorNameOrArtifact?: string | FunctionArtifact,
private publicKey: PublicKey,
private artifact: ContractArtifact,
private args: any[] = [],
initializerNameOrArtifact?: string | FunctionArtifact,
) {
super(publicKey, wallet, artifact, postDeployCtor, args, constructorNameOrArtifact);
super(pxe);
this.initializerArtifact = getInitializer(artifact, initializerNameOrArtifact);
}

/**
* Creates a TxExecutionRequest for deploying the contract.
* @param options - Prepares the contract for deployment by calling the request method and creating a TxExecutionRequest.
* @returns The TxExecutionRequest for deploying the contract.
*/
public async create(options: DeployOptions = {}): Promise<TxExecutionRequest> {
public async create(options: DeployAccountOptions = {}): Promise<TxExecutionRequest> {
if (this.txRequest) {
return this.txRequest;
}

const calls = await this.request(options);
if (calls.length === 0) {
throw new Error(`No function calls needed to deploy contract`);
if (!this.initializerArtifact) {
throw new Error('Account contract can not be initialized without an initializer');
}

const feePayload = await EntrypointPayload.fromFeeOptions(options.fee);
const feeAuthWit = await this.authWitnessProvider.createAuthWit(feePayload.hash());
await this.pxe.addCapsule(feePayload.toFields());

const execution = calls[0];
const packedArguments = PackedArguments.fromArgs(execution.args);
const instance = this.getInstance(options);
const initializerCall: FunctionCall = {
args: this.args,
functionData: FunctionData.fromAbi(this.initializerArtifact),
to: instance.address,
};

const { chainId, protocolVersion } = await this.wallet.getNodeInfo();
const packedArguments = PackedArguments.fromArgs(initializerCall.args);

const { chainId, protocolVersion } = await this.pxe.getNodeInfo();
const txContext = TxContext.empty(chainId, protocolVersion);
this.txRequest = new TxExecutionRequest(
execution.to,
execution.functionData,
initializerCall.to,
initializerCall.functionData,
packedArguments.hash,
txContext,
[packedArguments, ...feePayload.packedArguments],
[feeAuthWit],
);

await this.pxe.registerContract({ artifact: this.artifact, instance: this.getInstance() });
await this.pxe.registerContract({ artifact: this.artifact, instance });

return this.txRequest;
}

private getInstance(options: DeployAccountOptions): ContractInstanceWithAddress {
if (!this.instance) {
this.instance = getContractInstanceFromDeployParams(this.artifact, {
constructorArgs: this.args,
salt: options.contractAddressSalt,
portalAddress: options.portalContract,
publicKey: this.publicKey,
constructorArtifact: this.initializerArtifact,
deployer: AztecAddress.ZERO,
});
}

return this.instance;
}

/**
* Send the contract deployment transaction using the provided options.
* This function extends the 'send' method from the ContractFunctionInteraction class,
* allowing us to send a transaction specifically for contract deployment.
*
* @param options - An object containing various deployment options such as portalContract, contractAddressSalt, and from.
* @returns A SentTx object that returns the receipt and the deployed contract instance.
*/
public send(options: DeployAccountOptions = {}): DeploySentTx<TContract> {
const txHashPromise = super.send(options).getTxHash();
return new DeploySentTx(
this.pxe,
txHashPromise,
(address, wallet) => Contract.at(address, this.artifact, wallet) as Promise<TContract>,
this.getInstance(options),
);
}

/**
* Prove the request.
* @param options - Deployment options.
* @returns The proven tx.
*/
public prove(options: DeployAccountOptions): Promise<Tx> {
return super.prove(options);
}
}
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/entrypoint/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class EntrypointPayload {
* Serializes the payload to an array of fields
* @returns The fields of the payload
*/
toFields() {
toFields(): Fr[] {
return [
...this.#functionCalls.flatMap(call => [
call.args_hash,
Expand Down

0 comments on commit 51ed6a1

Please sign in to comment.