From be60eb3afbf65cb9c2dec2e912e398caffb2ebd0 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Fri, 22 Mar 2024 15:10:24 +0000 Subject: [PATCH] feat: add batched signerless contract calls (#5313) Adds a dedicated entrypoint for calls into contracts done from outside the context of an account contract. Previously we had just the `SignerlessWallet` that provided this functionality but it was limited to only one private function call per tx. This PR adds a dedicated contract to act as an entrypoint that takes the same payload as normal account contracts and calls external functions without doing any auth checks. --- noir-projects/noir-contracts/Nargo.toml | 1 + .../multi_call_entrypoint_contract/Nargo.toml | 9 ++ .../src/main.nr | 13 +++ .../src/defaults/account_interface.ts | 3 +- yarn-project/aztec.js/package.json | 1 + yarn-project/aztec.js/src/account/index.ts | 2 +- .../aztec.js/src/account/interface.ts | 25 +---- yarn-project/aztec.js/src/api/account.ts | 10 +- yarn-project/aztec.js/src/api/entrypoint.ts | 1 + .../src/contract/base_contract_interaction.ts | 2 +- .../src/entrypoint/default_entrypoint.ts | 27 ++++++ .../aztec.js/src/entrypoint/entrypoint.ts | 25 +++++ .../aztec.js/src/wallet/account_wallet.ts | 3 +- .../aztec.js/src/wallet/base_wallet.ts | 2 +- .../aztec.js/src/wallet/signerless_wallet.ts | 32 +++---- yarn-project/aztec/package.json | 1 + yarn-project/aztec/src/sandbox.ts | 17 +++- yarn-project/aztec/tsconfig.json | 3 + .../end-to-end/src/cli_docs_sandbox.test.ts | 1 + yarn-project/end-to-end/src/fixtures/utils.ts | 8 +- yarn-project/entrypoints/package.json | 4 +- .../entrypoints/src/account_entrypoint.ts | 3 +- .../entrypoints/src/dapp_entrypoint.ts | 3 +- .../entrypoints/src/entrypoint_payload.ts | 2 +- .../entrypoints/src/multi_call_entrypoint.ts | 91 +++++++++++++++++++ yarn-project/entrypoints/tsconfig.json | 3 + yarn-project/package.json | 1 + .../scripts/copy-contracts.sh | 3 +- .../src/multi-call-entrypoint/artifact.ts | 6 ++ .../src/multi-call-entrypoint/index.test.ts | 11 +++ .../src/multi-call-entrypoint/index.ts | 12 +++ .../pxe/src/pxe_service/create_pxe_service.ts | 2 + yarn-project/yarn.lock | 2 + 33 files changed, 260 insertions(+), 69 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr create mode 100644 yarn-project/aztec.js/src/api/entrypoint.ts create mode 100644 yarn-project/aztec.js/src/entrypoint/default_entrypoint.ts create mode 100644 yarn-project/aztec.js/src/entrypoint/entrypoint.ts create mode 100644 yarn-project/entrypoints/src/multi_call_entrypoint.ts create mode 100644 yarn-project/protocol-contracts/src/multi-call-entrypoint/artifact.ts create mode 100644 yarn-project/protocol-contracts/src/multi-call-entrypoint/index.test.ts create mode 100644 yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index e740f210ac7..1a8033bb29b 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -36,4 +36,5 @@ members = [ "contracts/token_bridge_contract", "contracts/uniswap_contract", "contracts/reader_contract", + "contracts/multi_call_entrypoint_contract", ] diff --git a/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/Nargo.toml new file mode 100644 index 00000000000..d71aacfaa8e --- /dev/null +++ b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "multi_call_entrypoint_contract" +authors = [""] +compiler_version = ">=0.18.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } +authwit = { path = "../../../aztec-nr/authwit" } diff --git a/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr new file mode 100644 index 00000000000..22d0d6ea39f --- /dev/null +++ b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr @@ -0,0 +1,13 @@ +// An entrypoint contract that allows everything to go through. Only used for testing +// Pair this with SignerlessWallet to perform multiple actions before any account contracts are deployed (and without authentication) +contract MultiCallEntrypoint { + use dep::std; + + use dep::aztec::prelude::AztecAddress; + use dep::authwit::entrypoint::app::AppPayload; + + #[aztec(private)] + fn entrypoint(app_payload: pub AppPayload) { + app_payload.execute_calls(&mut context); + } +} diff --git a/yarn-project/accounts/src/defaults/account_interface.ts b/yarn-project/accounts/src/defaults/account_interface.ts index e3a952f1540..e4bcf6abc5f 100644 --- a/yarn-project/accounts/src/defaults/account_interface.ts +++ b/yarn-project/accounts/src/defaults/account_interface.ts @@ -1,4 +1,5 @@ -import { AccountInterface, AuthWitnessProvider, EntrypointInterface, FeeOptions } from '@aztec/aztec.js/account'; +import { AccountInterface, AuthWitnessProvider } from '@aztec/aztec.js/account'; +import { EntrypointInterface, FeeOptions } from '@aztec/aztec.js/entrypoint'; import { AuthWitness, FunctionCall, TxExecutionRequest } from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress, Fr } from '@aztec/circuits.js'; import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account'; diff --git a/yarn-project/aztec.js/package.json b/yarn-project/aztec.js/package.json index 3bb05214e85..bcaa7ac78a5 100644 --- a/yarn-project/aztec.js/package.json +++ b/yarn-project/aztec.js/package.json @@ -10,6 +10,7 @@ "./account": "./dest/api/account.js", "./aztec_address": "./dest/api/aztec_address.js", "./deployment": "./dest/api/deployment.js", + "./entrypoint": "./dest/api/entrypoint.js", "./eth_address": "./dest/api/eth_address.js", "./ethereum": "./dest/api/ethereum.js", "./fee": "./dest/api/fee.js", diff --git a/yarn-project/aztec.js/src/account/index.ts b/yarn-project/aztec.js/src/account/index.ts index c3c7ca32743..19f673839ee 100644 --- a/yarn-project/aztec.js/src/account/index.ts +++ b/yarn-project/aztec.js/src/account/index.ts @@ -9,7 +9,7 @@ import { Fr } from '@aztec/circuits.js'; export { AccountContract } from './contract.js'; -export { AccountInterface, AuthWitnessProvider, EntrypointInterface, FeeOptions } from './interface.js'; +export { AccountInterface, AuthWitnessProvider } from './interface.js'; export * from './wallet.js'; /** A contract deployment salt. */ diff --git a/yarn-project/aztec.js/src/account/interface.ts b/yarn-project/aztec.js/src/account/interface.ts index 5cbe13430bf..4a3b90e2444 100644 --- a/yarn-project/aztec.js/src/account/interface.ts +++ b/yarn-project/aztec.js/src/account/interface.ts @@ -1,19 +1,9 @@ -import { AuthWitness, CompleteAddress, FunctionCall, TxExecutionRequest } from '@aztec/circuit-types'; +import { AuthWitness, CompleteAddress, FunctionCall } from '@aztec/circuit-types'; import { AztecAddress } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; -import { FeePaymentMethod } from '../fee/fee_payment_method.js'; - -/** - * Fee payment options for a transaction. - */ -export type FeeOptions = { - /** The fee payment method to use */ - paymentMethod: FeePaymentMethod; - /** The fee limit to pay */ - maxFee: bigint | number | Fr; -}; +import { EntrypointInterface } from '../entrypoint/entrypoint.js'; // docs:start:account-interface /** Creates authorization witnesses. */ @@ -44,17 +34,6 @@ export interface AuthWitnessProvider { ): Promise; } -/** Creates transaction execution requests out of a set of function calls. */ -export interface EntrypointInterface { - /** - * Generates an authenticated request out of set of function calls. - * @param executions - The execution intents to be run. - * @param feeOpts - The fee to be paid for the transaction. - * @returns The authenticated transaction execution request. - */ - createTxExecutionRequest(executions: FunctionCall[], feeOpts?: FeeOptions): Promise; -} - /** * Handler for interfacing with an account. Knows how to create transaction execution * requests and authorize actions for its corresponding account. diff --git a/yarn-project/aztec.js/src/api/account.ts b/yarn-project/aztec.js/src/api/account.ts index 6449edc6334..601e9045e5c 100644 --- a/yarn-project/aztec.js/src/api/account.ts +++ b/yarn-project/aztec.js/src/api/account.ts @@ -1,12 +1,4 @@ -export { - AccountContract, - AccountInterface, - AuthWitnessProvider, - EntrypointInterface, - Salt, - Wallet, - FeeOptions, -} from '../account/index.js'; +export { AccountContract, AccountInterface, AuthWitnessProvider, Salt, Wallet } from '../account/index.js'; export { AccountManager } from '../account_manager/index.js'; diff --git a/yarn-project/aztec.js/src/api/entrypoint.ts b/yarn-project/aztec.js/src/api/entrypoint.ts new file mode 100644 index 00000000000..dbe8196c6ef --- /dev/null +++ b/yarn-project/aztec.js/src/api/entrypoint.ts @@ -0,0 +1 @@ +export * from '../entrypoint/entrypoint.js'; 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 00b7f6e6920..f2949e50f71 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,6 @@ import { PXE, Tx, TxExecutionRequest } from '@aztec/circuit-types'; -import { FeeOptions } from '../account/interface.js'; +import { FeeOptions } from '../entrypoint/entrypoint.js'; import { SentTx } from './sent_tx.js'; /** diff --git a/yarn-project/aztec.js/src/entrypoint/default_entrypoint.ts b/yarn-project/aztec.js/src/entrypoint/default_entrypoint.ts new file mode 100644 index 00000000000..7315d4f6aa0 --- /dev/null +++ b/yarn-project/aztec.js/src/entrypoint/default_entrypoint.ts @@ -0,0 +1,27 @@ +import { FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types'; +import { TxContext } from '@aztec/circuits.js'; + +import { EntrypointInterface } from './entrypoint.js'; + +/** + * Default implementation of the entrypoint interface. It calls a function on a contract directly + */ +export class DefaultEntrypoint implements EntrypointInterface { + constructor(private chainId: number, private protocolVersion: number) {} + + createTxExecutionRequest(executions: FunctionCall[]): Promise { + const [execution] = executions; + const packedArguments = PackedArguments.fromArgs(execution.args); + const txContext = TxContext.empty(this.chainId, this.protocolVersion); + return Promise.resolve( + new TxExecutionRequest( + execution.to, + execution.functionData, + packedArguments.hash, + txContext, + [packedArguments], + [], + ), + ); + } +} diff --git a/yarn-project/aztec.js/src/entrypoint/entrypoint.ts b/yarn-project/aztec.js/src/entrypoint/entrypoint.ts new file mode 100644 index 00000000000..d32e9b768af --- /dev/null +++ b/yarn-project/aztec.js/src/entrypoint/entrypoint.ts @@ -0,0 +1,25 @@ +import { FunctionCall, TxExecutionRequest } from '@aztec/circuit-types'; +import { Fr } from '@aztec/foundation/fields'; + +import { FeePaymentMethod } from '../fee/fee_payment_method.js'; + +/** + * Fee payment options for a transaction. + */ +export type FeeOptions = { + /** The fee payment method to use */ + paymentMethod: FeePaymentMethod; + /** The fee limit to pay */ + maxFee: bigint | number | Fr; +}; + +/** Creates transaction execution requests out of a set of function calls. */ +export interface EntrypointInterface { + /** + * Generates an execution request out of set of function calls. + * @param executions - The execution intents to be run. + * @param feeOpts - The fee to be paid for the transaction. + * @returns The authenticated transaction execution request. + */ + createTxExecutionRequest(executions: FunctionCall[], feeOpts?: FeeOptions): Promise; +} diff --git a/yarn-project/aztec.js/src/wallet/account_wallet.ts b/yarn-project/aztec.js/src/wallet/account_wallet.ts index bc32d96c5ab..d2d1be43d4f 100644 --- a/yarn-project/aztec.js/src/wallet/account_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/account_wallet.ts @@ -2,8 +2,9 @@ import { AuthWitness, FunctionCall, PXE, TxExecutionRequest } from '@aztec/circu import { AztecAddress, Fr } from '@aztec/circuits.js'; import { ABIParameterVisibility, FunctionAbi, FunctionType } from '@aztec/foundation/abi'; -import { AccountInterface, FeeOptions } from '../account/interface.js'; +import { AccountInterface } from '../account/interface.js'; import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; +import { FeeOptions } from '../entrypoint/entrypoint.js'; import { computeAuthWitMessageHash } from '../utils/authwit.js'; import { BaseWallet } from './base_wallet.js'; diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 759e9180093..3402041b15a 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -19,9 +19,9 @@ import { ContractArtifact } from '@aztec/foundation/abi'; import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { NodeInfo } from '@aztec/types/interfaces'; -import { FeeOptions } from '../account/interface.js'; import { Wallet } from '../account/wallet.js'; import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; +import { FeeOptions } from '../entrypoint/entrypoint.js'; /** * A base class for Wallet implementations diff --git a/yarn-project/aztec.js/src/wallet/signerless_wallet.ts b/yarn-project/aztec.js/src/wallet/signerless_wallet.ts index e49febbd4e6..816b5a9e5ba 100644 --- a/yarn-project/aztec.js/src/wallet/signerless_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/signerless_wallet.ts @@ -1,30 +1,26 @@ -import { AuthWitness, FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types'; -import { CompleteAddress, Fr, TxContext } from '@aztec/circuits.js'; +import { AuthWitness, FunctionCall, PXE, TxExecutionRequest } from '@aztec/circuit-types'; +import { CompleteAddress, Fr } from '@aztec/circuits.js'; +import { DefaultEntrypoint } from '../entrypoint/default_entrypoint.js'; +import { EntrypointInterface } from '../entrypoint/entrypoint.js'; import { BaseWallet } from './base_wallet.js'; /** * Wallet implementation which creates a transaction request directly to the requested contract without any signing. */ export class SignerlessWallet extends BaseWallet { + constructor(pxe: PXE, private entrypoint?: EntrypointInterface) { + super(pxe); + } + async createTxExecutionRequest(executions: FunctionCall[]): Promise { - if (executions.length !== 1) { - throw new Error(`Unexpected number of executions. Expected 1 but received ${executions.length}.`); + let entrypoint = this.entrypoint; + if (!entrypoint) { + const { chainId, protocolVersion } = await this.pxe.getNodeInfo(); + entrypoint = new DefaultEntrypoint(chainId, protocolVersion); } - const [execution] = executions; - const packedArguments = PackedArguments.fromArgs(execution.args); - const { chainId, protocolVersion } = await this.pxe.getNodeInfo(); - const txContext = TxContext.empty(chainId, protocolVersion); - return Promise.resolve( - new TxExecutionRequest( - execution.to, - execution.functionData, - packedArguments.hash, - txContext, - [packedArguments], - [], - ), - ); + + return entrypoint.createTxExecutionRequest(executions); } getChainId(): Fr { diff --git a/yarn-project/aztec/package.json b/yarn-project/aztec/package.json index 6e5026a2017..f3f6f159b2c 100644 --- a/yarn-project/aztec/package.json +++ b/yarn-project/aztec/package.json @@ -33,6 +33,7 @@ "@aztec/aztec.js": "workspace:^", "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", + "@aztec/entrypoints": "workspace:^", "@aztec/ethereum": "workspace:^", "@aztec/foundation": "workspace:^", "@aztec/kv-store": "workspace:^", diff --git a/yarn-project/aztec/src/sandbox.ts b/yarn-project/aztec/src/sandbox.ts index 4549bc1adb1..b5ae376daa4 100644 --- a/yarn-project/aztec/src/sandbox.ts +++ b/yarn-project/aztec/src/sandbox.ts @@ -1,8 +1,9 @@ #!/usr/bin/env -S node --no-warnings import { AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node'; -import { AztecAddress, SignerlessWallet, Wallet } from '@aztec/aztec.js'; +import { AztecAddress, BatchCall, SignerlessWallet, Wallet } from '@aztec/aztec.js'; import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; import { AztecNode } from '@aztec/circuit-types'; +import { DefaultMultiCallEntrypoint } from '@aztec/entrypoints/multi-call'; import { DeployL1Contracts, L1ContractAddresses, @@ -165,8 +166,12 @@ async function deployCanonicalL2GasToken(deployer: Wallet, l1ContractAddresses: return; } - await (await registerContractClass(deployer, canonicalGasToken.artifact)).send().wait(); - await deployInstance(deployer, canonicalGasToken.instance).send().wait(); + const batch = new BatchCall(deployer, [ + (await registerContractClass(deployer, canonicalGasToken.artifact)).request(), + deployInstance(deployer, canonicalGasToken.instance).request(), + ]); + + await batch.send().wait(); logger(`Deployed Gas Token on L2 at ${canonicalGasToken.address}`); } @@ -200,8 +205,10 @@ export async function createSandbox(config: Partial = {}) { const pxe = await createAztecPXE(node); if (config.enableGas) { - const deployer = new SignerlessWallet(pxe); - await deployCanonicalL2GasToken(deployer, aztecNodeConfig.l1Contracts); + await deployCanonicalL2GasToken( + new SignerlessWallet(pxe, new DefaultMultiCallEntrypoint()), + aztecNodeConfig.l1Contracts, + ); } const stop = async () => { diff --git a/yarn-project/aztec/tsconfig.json b/yarn-project/aztec/tsconfig.json index 2f1632454b0..cb1f515a166 100644 --- a/yarn-project/aztec/tsconfig.json +++ b/yarn-project/aztec/tsconfig.json @@ -24,6 +24,9 @@ { "path": "../circuits.js" }, + { + "path": "../entrypoints" + }, { "path": "../ethereum" }, diff --git a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts index 67a70c8cd54..ffb3a058b0b 100644 --- a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts +++ b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts @@ -117,6 +117,7 @@ GasTokenContractArtifact ImportTestContractArtifact InclusionProofsContractArtifact LendingContractArtifact +MultiCallEntrypointContractArtifact ParentContractArtifact PendingNoteHashesContractArtifact PriceFeedContractArtifact diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 9474215d5ed..9aaddc20197 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -17,6 +17,7 @@ import { LogType, PXE, SentTx, + SignerlessWallet, Wallet, createAztecNodeClient, createDebugLogger, @@ -27,6 +28,7 @@ import { waitForPXE, } from '@aztec/aztec.js'; import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; +import { DefaultMultiCallEntrypoint } from '@aztec/entrypoints/multi-call'; import { randomBytes } from '@aztec/foundation/crypto'; import { AvailabilityOracleAbi, @@ -264,7 +266,7 @@ async function setupWithRemoteEnvironment( if (['1', 'true'].includes(ENABLE_GAS)) { // this contract might already have been deployed // the following function is idempotent - await deployCanonicalGasToken(wallets[0]); + await deployCanonicalGasToken(new SignerlessWallet(pxeClient, new DefaultMultiCallEntrypoint())); } return { @@ -370,9 +372,7 @@ export async function setup( const { pxe, accounts, wallets } = await setupPXEService(numberOfAccounts, aztecNode!, pxeOpts, logger); if (['1', 'true'].includes(ENABLE_GAS)) { - // this should be a neutral wallet, but the SignerlessWallet only accepts a single function call - // and this needs two: one to register the class and another to deploy the instance - await deployCanonicalGasToken(wallets[0]); + await deployCanonicalGasToken(new SignerlessWallet(pxe, new DefaultMultiCallEntrypoint())); } const cheatCodes = CheatCodes.create(config.rpcUrl, pxe!); diff --git a/yarn-project/entrypoints/package.json b/yarn-project/entrypoints/package.json index 7d904b8700b..586e59570b5 100644 --- a/yarn-project/entrypoints/package.json +++ b/yarn-project/entrypoints/package.json @@ -6,7 +6,8 @@ "type": "module", "exports": { "./dapp": "./dest/dapp_entrypoint.js", - "./account": "./dest/account_entrypoint.js" + "./account": "./dest/account_entrypoint.js", + "./multi-call": "./dest/multi_call_entrypoint.js" }, "typedocOptions": { "entryPoints": [ @@ -40,6 +41,7 @@ "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", "@aztec/foundation": "workspace:^", + "@aztec/protocol-contracts": "workspace:^", "tslib": "^2.4.0" }, "devDependencies": { diff --git a/yarn-project/entrypoints/src/account_entrypoint.ts b/yarn-project/entrypoints/src/account_entrypoint.ts index c0c20c8089e..e855e48324f 100644 --- a/yarn-project/entrypoints/src/account_entrypoint.ts +++ b/yarn-project/entrypoints/src/account_entrypoint.ts @@ -1,4 +1,5 @@ -import { AuthWitnessProvider, EntrypointInterface, FeeOptions } from '@aztec/aztec.js/account'; +import { AuthWitnessProvider } from '@aztec/aztec.js/account'; +import { EntrypointInterface, FeeOptions } from '@aztec/aztec.js/entrypoint'; import { FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types'; import { AztecAddress, FunctionData, GeneratorIndex, TxContext } from '@aztec/circuits.js'; import { FunctionAbi, encodeArguments } from '@aztec/foundation/abi'; diff --git a/yarn-project/entrypoints/src/dapp_entrypoint.ts b/yarn-project/entrypoints/src/dapp_entrypoint.ts index 0cb82b372b2..5e851d59f44 100644 --- a/yarn-project/entrypoints/src/dapp_entrypoint.ts +++ b/yarn-project/entrypoints/src/dapp_entrypoint.ts @@ -1,5 +1,6 @@ import { computeInnerAuthWitHash, computeOuterAuthWitHash } from '@aztec/aztec.js'; -import { AuthWitnessProvider, EntrypointInterface } from '@aztec/aztec.js/account'; +import { AuthWitnessProvider } from '@aztec/aztec.js/account'; +import { EntrypointInterface } from '@aztec/aztec.js/entrypoint'; import { FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types'; import { AztecAddress, Fr, FunctionData, TxContext } from '@aztec/circuits.js'; import { FunctionAbi, encodeArguments } from '@aztec/foundation/abi'; diff --git a/yarn-project/entrypoints/src/entrypoint_payload.ts b/yarn-project/entrypoints/src/entrypoint_payload.ts index 654694847e3..644af140d89 100644 --- a/yarn-project/entrypoints/src/entrypoint_payload.ts +++ b/yarn-project/entrypoints/src/entrypoint_payload.ts @@ -1,4 +1,4 @@ -import { FeeOptions } from '@aztec/aztec.js/account'; +import { FeeOptions } from '@aztec/aztec.js/entrypoint'; import { Fr } from '@aztec/aztec.js/fields'; import { FunctionCall, PackedArguments, emptyFunctionCall } from '@aztec/circuit-types'; import { AztecAddress } from '@aztec/circuits.js'; diff --git a/yarn-project/entrypoints/src/multi_call_entrypoint.ts b/yarn-project/entrypoints/src/multi_call_entrypoint.ts new file mode 100644 index 00000000000..59431343878 --- /dev/null +++ b/yarn-project/entrypoints/src/multi_call_entrypoint.ts @@ -0,0 +1,91 @@ +import { EntrypointInterface } from '@aztec/aztec.js/entrypoint'; +import { FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types'; +import { AztecAddress, FunctionData, TxContext } from '@aztec/circuits.js'; +import { FunctionAbi, encodeArguments } from '@aztec/foundation/abi'; +import { getCanonicalMultiCallEntrypointAddress } from '@aztec/protocol-contracts/multi-call-entrypoint'; + +import { DEFAULT_CHAIN_ID, DEFAULT_VERSION } from './constants.js'; +import { buildAppPayload } from './entrypoint_payload.js'; + +/** + * Implementation for an entrypoint interface that can execute multiple function calls in a single transaction + */ +export class DefaultMultiCallEntrypoint implements EntrypointInterface { + constructor( + private address: AztecAddress = getCanonicalMultiCallEntrypointAddress(), + private chainId: number = DEFAULT_CHAIN_ID, + private version: number = DEFAULT_VERSION, + ) {} + + createTxExecutionRequest(executions: FunctionCall[]): Promise { + const { payload: appPayload, packedArguments: appPackedArguments } = buildAppPayload(executions); + + const abi = this.getEntrypointAbi(); + const entrypointPackedArgs = PackedArguments.fromArgs(encodeArguments(abi, [appPayload])); + + const txRequest = TxExecutionRequest.from({ + argsHash: entrypointPackedArgs.hash, + origin: this.address, + functionData: FunctionData.fromAbi(abi), + txContext: TxContext.empty(this.chainId, this.version), + packedArguments: [...appPackedArguments, entrypointPackedArgs], + authWitnesses: [], + }); + + return Promise.resolve(txRequest); + } + + private getEntrypointAbi() { + return { + name: 'entrypoint', + isInitializer: false, + functionType: 'secret', + isInternal: false, + parameters: [ + { + name: 'app_payload', + type: { + kind: 'struct', + path: 'authwit::entrypoint::app::AppPayload', + fields: [ + { + name: 'function_calls', + type: { + kind: 'array', + length: 4, + type: { + kind: 'struct', + path: 'authwit::entrypoint::function_call::FunctionCall', + fields: [ + { name: 'args_hash', type: { kind: 'field' } }, + { + name: 'function_selector', + type: { + kind: 'struct', + path: 'authwit::aztec::protocol_types::abis::function_selector::FunctionSelector', + fields: [{ name: 'inner', type: { kind: 'integer', sign: 'unsigned', width: 32 } }], + }, + }, + { + name: 'target_address', + type: { + kind: 'struct', + path: 'authwit::aztec::protocol_types::address::AztecAddress', + fields: [{ name: 'inner', type: { kind: 'field' } }], + }, + }, + { name: 'is_public', type: { kind: 'boolean' } }, + ], + }, + }, + }, + { name: 'nonce', type: { kind: 'field' } }, + ], + }, + visibility: 'public', + }, + ], + returnTypes: [], + } as FunctionAbi; + } +} diff --git a/yarn-project/entrypoints/tsconfig.json b/yarn-project/entrypoints/tsconfig.json index 63d75e05aa8..86797852e11 100644 --- a/yarn-project/entrypoints/tsconfig.json +++ b/yarn-project/entrypoints/tsconfig.json @@ -17,6 +17,9 @@ }, { "path": "../foundation" + }, + { + "path": "../protocol-contracts" } ], "include": ["src", "src/**/*.json"] diff --git a/yarn-project/package.json b/yarn-project/package.json index 5e5fbfb0a68..099b942e593 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -32,6 +32,7 @@ "cli", "docs", "end-to-end", + "entrypoints", "ethereum", "foundation", "key-store", diff --git a/yarn-project/protocol-contracts/scripts/copy-contracts.sh b/yarn-project/protocol-contracts/scripts/copy-contracts.sh index bbb90ae2378..14a227c80a0 100755 --- a/yarn-project/protocol-contracts/scripts/copy-contracts.sh +++ b/yarn-project/protocol-contracts/scripts/copy-contracts.sh @@ -6,8 +6,9 @@ contracts=( contract_class_registerer_contract-ContractClassRegisterer contract_instance_deployer_contract-ContractInstanceDeployer gas_token_contract-GasToken + multi_call_entrypoint_contract-MultiCallEntrypoint ) for contract in "${contracts[@]}"; do cp "../../noir-projects/noir-contracts/target/$contract.json" ./src/artifacts/${contract#*-}.json -done \ No newline at end of file +done diff --git a/yarn-project/protocol-contracts/src/multi-call-entrypoint/artifact.ts b/yarn-project/protocol-contracts/src/multi-call-entrypoint/artifact.ts new file mode 100644 index 00000000000..ede18eb397b --- /dev/null +++ b/yarn-project/protocol-contracts/src/multi-call-entrypoint/artifact.ts @@ -0,0 +1,6 @@ +import { loadContractArtifact } from '@aztec/types/abi'; +import { NoirCompiledContract } from '@aztec/types/noir'; + +import MultiCallEntrypoint from '../artifacts/MultiCallEntrypoint.json' assert { type: 'json' }; + +export const MultiCallEntrypointArtifact = loadContractArtifact(MultiCallEntrypoint as NoirCompiledContract); diff --git a/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.test.ts b/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.test.ts new file mode 100644 index 00000000000..5affdff9e13 --- /dev/null +++ b/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.test.ts @@ -0,0 +1,11 @@ +import { computeContractAddressFromInstance, getContractClassFromArtifact } from '@aztec/circuits.js'; + +import { getCanonicalMultiCallEntrypointContract } from './index.js'; + +describe('MultiCallEntrypoint', () => { + it('returns canonical protocol contract', () => { + const contract = getCanonicalMultiCallEntrypointContract(); + expect(computeContractAddressFromInstance(contract.instance)).toEqual(contract.address); + expect(getContractClassFromArtifact(contract.artifact).id).toEqual(contract.contractClass.id); + }); +}); diff --git a/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts b/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts new file mode 100644 index 00000000000..c9298b886f6 --- /dev/null +++ b/yarn-project/protocol-contracts/src/multi-call-entrypoint/index.ts @@ -0,0 +1,12 @@ +import { AztecAddress, EthAddress, Point } from '@aztec/circuits.js'; + +import { ProtocolContract, getCanonicalProtocolContract } from '../protocol_contract.js'; +import { MultiCallEntrypointArtifact } from './artifact.js'; + +export function getCanonicalMultiCallEntrypointContract(): ProtocolContract { + return getCanonicalProtocolContract(MultiCallEntrypointArtifact, 1, [], Point.ZERO, EthAddress.ZERO); +} + +export function getCanonicalMultiCallEntrypointAddress(): AztecAddress { + return getCanonicalMultiCallEntrypointContract().address; +} diff --git a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts index c2cba52f151..12813a382b1 100644 --- a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts @@ -7,6 +7,7 @@ import { initStoreForRollup } from '@aztec/kv-store/utils'; import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer'; import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token'; import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer'; +import { getCanonicalMultiCallEntrypointContract } from '@aztec/protocol-contracts/multi-call-entrypoint'; import { join } from 'path'; @@ -46,6 +47,7 @@ export async function createPXEService( for (const contract of [ getCanonicalClassRegisterer(), getCanonicalInstanceDeployer(), + getCanonicalMultiCallEntrypointContract(), getCanonicalGasToken(l1Contracts.gasPortalAddress), ]) { await server.registerContract(contract); diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index a7437ba5451..d29b415fdfa 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -228,6 +228,7 @@ __metadata: "@aztec/aztec.js": "workspace:^" "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" + "@aztec/entrypoints": "workspace:^" "@aztec/ethereum": "workspace:^" "@aztec/foundation": "workspace:^" "@aztec/kv-store": "workspace:^" @@ -441,6 +442,7 @@ __metadata: "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" "@aztec/foundation": "workspace:^" + "@aztec/protocol-contracts": "workspace:^" "@jest/globals": ^29.5.0 "@types/jest": ^29.5.0 jest: ^29.5.0