diff --git a/yarn-project/accounts/src/testing/configuration.ts b/yarn-project/accounts/src/testing/configuration.ts index a7e9b2c160b..0b3ff8df28f 100644 --- a/yarn-project/accounts/src/testing/configuration.ts +++ b/yarn-project/accounts/src/testing/configuration.ts @@ -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'; @@ -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(); diff --git a/yarn-project/accounts/src/testing/create_account.ts b/yarn-project/accounts/src/testing/create_account.ts index 9b409740b6c..b47305c28d3 100644 --- a/yarn-project/accounts/src/testing/create_account.ts +++ b/yarn-project/accounts/src/testing/create_account.ts @@ -31,9 +31,6 @@ export async function createAccounts(pxe: PXE, numberOfAccounts = 1): Promise d.prove({ contractAddressSalt: account.salt, - skipClassRegistration: true, - skipPublicDeployment: true, - universalDeploy: true, }), ); accounts.push(account); diff --git a/yarn-project/aztec.js/src/account_manager/index.ts b/yarn-project/aztec.js/src/account_manager/index.ts index a86af8bc631..18e84a0acf3 100644 --- a/yarn-project/aztec.js/src/account_manager/index.ts +++ b/yarn-project/aztec.js/src/account_manager/index.ts @@ -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'; /** @@ -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, @@ -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, ); } @@ -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()); diff --git a/yarn-project/aztec.js/src/contract/deploy_account_method.ts b/yarn-project/aztec.js/src/contract/deploy_account_method.ts index 957ae58c2e0..d717bb71d9e 100644 --- a/yarn-project/aztec.js/src/contract/deploy_account_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_account_method.ts @@ -1,29 +1,52 @@ -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 extends DeployMethod { +export class DeployAccountMethod 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, - 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); } /** @@ -31,35 +54,83 @@ export class DeployAccountMethod exte * @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 { + public async create(options: DeployAccountOptions = {}): Promise { 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 { + const txHashPromise = super.send(options).getTxHash(); + return new DeploySentTx( + this.pxe, + txHashPromise, + (address, wallet) => Contract.at(address, this.artifact, wallet) as Promise, + this.getInstance(options), + ); + } + + /** + * Prove the request. + * @param options - Deployment options. + * @returns The proven tx. + */ + public prove(options: DeployAccountOptions): Promise { + return super.prove(options); + } } diff --git a/yarn-project/aztec.js/src/entrypoint/payload.ts b/yarn-project/aztec.js/src/entrypoint/payload.ts index d43961fc01b..c5c9fba449c 100644 --- a/yarn-project/aztec.js/src/entrypoint/payload.ts +++ b/yarn-project/aztec.js/src/entrypoint/payload.ts @@ -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,