diff --git a/boxes/boxes/vanilla/app/embedded-wallet.ts b/boxes/boxes/vanilla/app/embedded-wallet.ts index bbf32271f6b5..2bce19d8640f 100644 --- a/boxes/boxes/vanilla/app/embedded-wallet.ts +++ b/boxes/boxes/vanilla/app/embedded-wallet.ts @@ -19,6 +19,7 @@ import { EmbeddedWallet as EmbeddedWalletBase, type EmbeddedWalletOptions, } from '@aztec/wallets/embedded'; +import { NO_FROM, NoFrom } from '@aztec/aztec.js/account'; const logger = createLogger('wallet'); const LocalStorageKey = 'aztec-account'; @@ -43,7 +44,7 @@ export class EmbeddedWallet extends EmbeddedWalletBase { * @returns - Complete fee options that can be used to create a transaction execution request */ override async completeFeeOptions( - from: AztecAddress, + from: AztecAddress | NoFrom, feePayer?: AztecAddress, gasSettings?: Partial> ): Promise { @@ -52,21 +53,26 @@ export class EmbeddedWallet extends EmbeddedWalletBase { (await this.aztecNode.getCurrentMinFees()).mul(1 + this.minFeePadding); let accountFeePaymentMethodOptions; let walletFeePaymentMethod; - // The transaction does not include a fee payment method, so we - // use the sponsoredFPC - if (!feePayer) { - accountFeePaymentMethodOptions = AccountFeePaymentMethodOptions.EXTERNAL; - const sponsoredFPCAddress = await this.#getSponsoredFPCAddress(); - - walletFeePaymentMethod = new SponsoredFeePaymentMethod( - sponsoredFPCAddress - ); - } else { - // The transaction includes fee payment method, so we check if we are the fee payer for it - // (this can only happen if the embedded payment method is FeeJuiceWithClaim) - accountFeePaymentMethodOptions = from.equals(feePayer) - ? AccountFeePaymentMethodOptions.FEE_JUICE_WITH_CLAIM - : AccountFeePaymentMethodOptions.EXTERNAL; + // If from is an address, we need to determine the appropriate fee payment method options for the + // account contract entrypoint to use + if (from !== NO_FROM) { + // The transaction does not include a fee payment method, so we + // use the sponsoredFPC + if (!feePayer) { + accountFeePaymentMethodOptions = + AccountFeePaymentMethodOptions.EXTERNAL; + const sponsoredFPCAddress = await this.#getSponsoredFPCAddress(); + + walletFeePaymentMethod = new SponsoredFeePaymentMethod( + sponsoredFPCAddress + ); + } else { + // The transaction includes fee payment method, so we check if we are the fee payer for it + // (this can only happen if the embedded payment method is FeeJuiceWithClaim) + accountFeePaymentMethodOptions = from.equals(feePayer) + ? AccountFeePaymentMethodOptions.FEE_JUICE_WITH_CLAIM + : AccountFeePaymentMethodOptions.EXTERNAL; + } } const fullGasSettings: GasSettings = GasSettings.default({ ...gasSettings, @@ -153,7 +159,7 @@ export class EmbeddedWallet extends EmbeddedWalletBase { const sponsoredFPCAddress = await this.#getSponsoredFPCAddress(); const deployOpts: DeployAccountOptions = { - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: new SponsoredFeePaymentMethod(sponsoredFPCAddress), }, diff --git a/boxes/boxes/vanilla/scripts/deploy.ts b/boxes/boxes/vanilla/scripts/deploy.ts index e0e0f971a739..83a24cc3f913 100644 --- a/boxes/boxes/vanilla/scripts/deploy.ts +++ b/boxes/boxes/vanilla/scripts/deploy.ts @@ -19,6 +19,7 @@ import fs from 'fs'; import path from 'path'; // @ts-ignore import { PrivateVotingContract } from '../artifacts/PrivateVoting.ts'; +import { NO_FROM } from '@aztec/aztec.js/account'; const AZTEC_NODE_URL = process.env.AZTEC_NODE_URL || 'http://localhost:8080'; const WRITE_ENV_FILE = process.env.WRITE_ENV_FILE === 'false' ? false : true; @@ -51,7 +52,7 @@ async function createAccount(wallet: EmbeddedWallet) { const deployMethod = await accountManager.getDeployMethod(); const sponsoredPFCContract = await getSponsoredPFCContract(); const deployOpts: DeployAccountOptions = { - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: new SponsoredFeePaymentMethod( sponsoredPFCContract.address @@ -71,7 +72,10 @@ async function deployContract(wallet: Wallet, deployer: AztecAddress) { const sponsoredPFCContract = await getSponsoredPFCContract(); - const { contract } = await PrivateVotingContract.deploy(wallet, deployer).send({ + const { contract } = await PrivateVotingContract.deploy( + wallet, + deployer + ).send({ from: deployer, contractAddressSalt: salt, fee: { diff --git a/docs/docs-developers/docs/aztec-js/how_to_create_account.md b/docs/docs-developers/docs/aztec-js/how_to_create_account.md index f4b1f36ccd06..53d55391814d 100644 --- a/docs/docs-developers/docs/aztec-js/how_to_create_account.md +++ b/docs/docs-developers/docs/aztec-js/how_to_create_account.md @@ -50,7 +50,7 @@ If your account already has Fee Juice (for example, [bridged from L1](./how_to_p #include_code deploy_account_fee_juice /docs/examples/ts/aztecjs_connection/index.ts typescript -The `from: AztecAddress.ZERO` is required because there's no existing account to send from—the transaction itself creates the account. +The `from: NO_FROM` signals that this transaction should be executed without account contract mediation. The wallet will directly execute it via a default entrypoint with no authorization ## Verify deployment diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index 130d4fe115b4..976340152bb9 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -9,6 +9,62 @@ Aztec is in active development. Each version may introduce breaking changes that ## TBD +### [Aztec.js] Use `NO_FROM` instead of `AztecAddress.ZERO` to bypass account contract entrypoint + +When sending transactions that should not be mediated by an account contract (e.g., account contract self-deployments), use the explicit `NO_FROM` sentinel instead of `AztecAddress.ZERO`. + +`NO_FROM` signals that the transaction should be executed directly via the `DefaultEntrypoint`. This replaces the brittle convention of passing `AztecAddress.ZERO` as the `from` field. + +**Migration:** + +```diff +- import { AztecAddress } from '@aztec/aztec.js'; ++ import { NO_FROM } from '@aztec/aztec.js/account'; + + await contract.methods.my_method().send({ +- from: AztecAddress.ZERO, ++ from: NO_FROM, + }); +``` + +Note that `DefaultEntrypoint` only accepts a single call. If you need to execute multiple calls without account contract mediation (e.g., deploying an account contract and paying a fee in the same transaction), wrap them through `DefaultMultiCallEntrypoint` on the app side before sending: + +```typescript +import { NO_FROM } from "@aztec/aztec.js/account"; +import { DefaultMultiCallEntrypoint } from "@aztec/entrypoints/multicall"; +import { mergeExecutionPayloads } from "@aztec/stdlib/tx"; + +// Merge multiple execution payloads into one +const merged = mergeExecutionPayloads([deployPayload, feePayload]); + +// Wrap through multicall so it becomes a single call for DefaultEntrypoint +const multicall = new DefaultMultiCallEntrypoint(); +const chainInfo = await wallet.getChainInfo(); +const wrappedPayload = await multicall.wrapExecutionPayload(merged, chainInfo); + +// Send without account contract mediation +await wallet.sendTx(wrappedPayload, { from: NO_FROM }); +``` + +Using other contracts for wrapping (for example, supporting more calls) is also supported, as long as the contract is registered in the wallet. This opens the door to different flows that do not use account entrypoints as the first call in the chain, including app sponsored FPCs. + +**Impact**: Any code that passes `AztecAddress.ZERO` as the `from` option in `.send()`, `.simulate()`, or deploy options must switch to `NO_FROM`. Wallets use `DefaultEntrypoint` directly for `NO_FROM` transactions, instead of the `DefaultMultiCallEntrypoint` that was used internally before when specifying `AztecAddress.ZERO`. + +### [Aztec.js] `ExecuteUtilityOptions.scope` renamed to `scopes` and type changed to `AztecAddress[]` + +The `scope` field in `ExecuteUtilityOptions` has been renamed to `scopes` and changed from a single `AztecAddress` to `AztecAddress[]`. This aligns the wallet's `executeUtility` API with the PXE API and `sendTx` in `Wallet`, which both accept an array of scopes. + +**Migration:** + +```diff + wallet.executeUtility(call, { +- scope: myAddress, ++ scopes: [myAddress], + }); +``` + +**Impact**: Any code that calls `wallet.executeUtility` directly must update the options object. Wallets must update to adapt to the new interface + ### [Aztec.nr] `attempt_note_discovery` now takes two separate functions instead of one The `attempt_note_discovery` function (and related discovery functions like `do_sync_state`, `process_message_ciphertext`) now takes separate `compute_note_hash` and `compute_note_nullifier` arguments instead of a single combined `compute_note_hash_and_nullifier`. The corresponding type aliases are now `ComputeNoteHash` and `ComputeNoteNullifier` (instead of `ComputeNoteHashAndNullifier`). diff --git a/docs/examples/ts/aztecjs_connection/index.ts b/docs/examples/ts/aztecjs_connection/index.ts index a014603037d9..77f560ca0ca5 100644 --- a/docs/examples/ts/aztecjs_connection/index.ts +++ b/docs/examples/ts/aztecjs_connection/index.ts @@ -55,7 +55,7 @@ console.log("New account address:", newAccount.address.toString()); // docs:start:deploy_account_sponsored_fpc // Additional imports needed for account deployment examples -import { AztecAddress } from "@aztec/aztec.js/addresses"; +import { NO_FROM } from "@aztec/aztec.js/account"; import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee/testing"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; import { getContractInstanceFromInstantiationParams } from "@aztec/stdlib/contract"; @@ -76,7 +76,7 @@ const sponsoredPaymentMethod = new SponsoredFeePaymentMethod( // newAccount is the account created in the previous section const deployMethod = await newAccount.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: sponsoredPaymentMethod }, }); // docs:end:deploy_account_sponsored_fpc @@ -144,7 +144,7 @@ console.log(`Alice's token balance: ${balance}`); // Deploy the account using the bridged Fee Juice const deployMethodFeeJuice = await feeJuiceAccount.getDeployMethod(); await deployMethodFeeJuice.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: new FeeJuicePaymentMethodWithClaim( feeJuiceAccount.address, diff --git a/docs/examples/ts/recursive_verification/index.ts b/docs/examples/ts/recursive_verification/index.ts index 4ded9c585249..cf404ead9925 100644 --- a/docs/examples/ts/recursive_verification/index.ts +++ b/docs/examples/ts/recursive_verification/index.ts @@ -6,7 +6,7 @@ import { getSponsoredFPCInstance } from "./scripts/sponsored_fpc.js"; import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC"; import { ValueNotEqualContract } from "./artifacts/ValueNotEqual.js"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; -import { AztecAddress } from "@aztec/aztec.js/addresses"; +import { NO_FROM } from "@aztec/aztec.js/account"; import { Fr } from "@aztec/aztec.js/fields"; import assert from "node:assert"; import fs from "node:fs"; @@ -14,7 +14,9 @@ import fs from "node:fs"; // docs:start:sample_data if (!fs.existsSync("data.json")) { - console.error("data.json not found. Run 'yarn data' first to generate proof data."); + console.error( + "data.json not found. Run 'yarn data' first to generate proof data.", + ); process.exit(1); } const data = JSON.parse(fs.readFileSync("data.json", "utf-8")); @@ -57,7 +59,7 @@ async function main() { // Deploy the account contract const deployMethod = await manager.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: sponsoredPaymentMethod }, }); @@ -84,9 +86,11 @@ async function main() { // Step 3: Read initial counter value // simulate() executes without submitting a transaction - let counterValue = (await valueNotEqual.methods - .get_counter(accounts[0].item) - .simulate({ from: accounts[0].item })).result; + let counterValue = ( + await valueNotEqual.methods + .get_counter(accounts[0].item) + .simulate({ from: accounts[0].item }) + ).result; console.log(`Counter value: ${counterValue}`); // Should be 10 // Step 4: Call increment() with proof data @@ -107,9 +111,11 @@ async function main() { await interaction.send(opts); // Step 6: Read updated counter - counterValue = (await valueNotEqual.methods - .get_counter(accounts[0].item) - .simulate({ from: accounts[0].item })).result; + counterValue = ( + await valueNotEqual.methods + .get_counter(accounts[0].item) + .simulate({ from: accounts[0].item }) + ).result; console.log(`Counter value: ${counterValue}`); // Should be 11 assert(counterValue === 11n, "Counter should be 11 after verification"); diff --git a/noir-projects/aztec-nr/aztec/src/authwit/auth.nr b/noir-projects/aztec-nr/aztec/src/authwit/auth.nr index 26a60fe0eb25..c62d4c5172e6 100644 --- a/noir-projects/aztec-nr/aztec/src/authwit/auth.nr +++ b/noir-projects/aztec-nr/aztec/src/authwit/auth.nr @@ -213,6 +213,7 @@ struct CallAuthorization { struct CallAuthorizationRequest { selector: AuthorizationSelector, inner_hash: Field, + on_behalf_of: AztecAddress, msg_sender: AztecAddress, fn_selector: FunctionSelector, args_hash: Field, @@ -221,11 +222,13 @@ struct CallAuthorizationRequest { unconstrained fn emit_authorization_as_offchain_effect( authorization: CallAuthorization, inner_hash: Field, + on_behalf_of: AztecAddress, ) { let args: [Field; N] = load(authorization.args_hash); let authorization_request = CallAuthorizationRequest { selector: authorization.get_authorization_selector(), inner_hash: inner_hash, + on_behalf_of: on_behalf_of, msg_sender: authorization.msg_sender, fn_selector: authorization.selector, args_hash: authorization.args_hash, @@ -253,7 +256,7 @@ pub fn assert_current_call_valid_authwit(context: &mut PrivateContex let inner_hash = compute_inner_authwit_hash(authorization.serialize()); // Safety: Offchain effects are by definition unconstrained. They are emitted via an oracle which we don't use for // anything besides its side effects, therefore this is safe to call. - unsafe { emit_authorization_as_offchain_effect::(authorization, inner_hash) }; + unsafe { emit_authorization_as_offchain_effect::(authorization, inner_hash, on_behalf_of) }; assert_inner_hash_valid_authwit(context, on_behalf_of, inner_hash); } diff --git a/playground/src/components/home/components/Landing.tsx b/playground/src/components/home/components/Landing.tsx index 8bb921d9902c..1b007362104c 100644 --- a/playground/src/components/home/components/Landing.tsx +++ b/playground/src/components/home/components/Landing.tsx @@ -18,6 +18,7 @@ import { trackButtonClick } from '../../../utils/matomo'; import type { EmbeddedWallet } from '@aztec/wallets/embedded'; import { prepareForFeePayment } from '../../../utils/sponsoredFPC'; import { colors, commonStyles } from '../../../global.styles'; +import { NO_FROM } from '@aztec/aztec.js/account'; const container = css({ display: 'flex', @@ -399,7 +400,7 @@ export function Landing() { const deployMethod = await accountManager.getDeployMethod(); const opts: DeployAccountOptions = { - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod, }, diff --git a/playground/src/wallet/components/CreateAccountDialog.tsx b/playground/src/wallet/components/CreateAccountDialog.tsx index ab1a7672b796..021db24b59c6 100644 --- a/playground/src/wallet/components/CreateAccountDialog.tsx +++ b/playground/src/wallet/components/CreateAccountDialog.tsx @@ -24,6 +24,7 @@ import { INFO_TEXT } from '../../constants'; import { Box, DialogContent } from '@mui/material'; import { DialogActions } from '@mui/material'; import type { EmbeddedWallet } from '@aztec/wallets/embedded'; +import { NO_FROM } from '@aztec/aztec.js/account'; export function CreateAccountDialog({ wallet, @@ -77,7 +78,7 @@ export function CreateAccountDialog({ if (publiclyDeploy) { deployMethod = await accountManager.getDeployMethod(); opts = { - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: feePaymentMethod, }, diff --git a/yarn-project/aztec.js/src/account/signerless_account.ts b/yarn-project/aztec.js/src/account/signerless_account.ts deleted file mode 100644 index 6b1962dc6690..000000000000 --- a/yarn-project/aztec.js/src/account/signerless_account.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { ChainInfo, EntrypointInterface } from '@aztec/entrypoints/interfaces'; -import { DefaultMultiCallEntrypoint } from '@aztec/entrypoints/multicall'; -import type { Fr } from '@aztec/foundation/curves/bn254'; -import { AuthWitness } from '@aztec/stdlib/auth-witness'; -import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { CompleteAddress } from '@aztec/stdlib/contract'; -import type { GasSettings } from '@aztec/stdlib/gas'; -import type { ExecutionPayload, TxExecutionRequest } from '@aztec/stdlib/tx'; - -import type { CallIntent, IntentInnerHash } from '../utils/authwit.js'; -import type { Account } from './account.js'; - -/** - * Account implementation which creates a transaction using the multicall protocol contract as entrypoint. - */ -export class SignerlessAccount implements Account { - private entrypoint: EntrypointInterface; - - constructor() { - this.entrypoint = new DefaultMultiCallEntrypoint(); - } - - createTxExecutionRequest( - exec: ExecutionPayload, - gasSettings: GasSettings, - chainInfo: ChainInfo, - ): Promise { - return this.entrypoint.createTxExecutionRequest(exec, gasSettings, chainInfo); - } - - wrapExecutionPayload(exec: ExecutionPayload, chainInfo: ChainInfo, options?: any): Promise { - return this.entrypoint.wrapExecutionPayload(exec, chainInfo, options); - } - - createAuthWit(_intent: Fr | Buffer | IntentInnerHash | CallIntent): Promise { - throw new Error('SignerlessAccount: Method createAuthWit not implemented.'); - } - - getCompleteAddress(): CompleteAddress { - throw new Error('SignerlessAccount: Method getCompleteAddress not implemented.'); - } - - getAddress(): AztecAddress { - throw new Error('SignerlessAccount: Method getAddress not implemented.'); - } -} diff --git a/yarn-project/aztec.js/src/api/account.ts b/yarn-project/aztec.js/src/api/account.ts index cbb8000c3bc2..e809af3623fb 100644 --- a/yarn-project/aztec.js/src/api/account.ts +++ b/yarn-project/aztec.js/src/api/account.ts @@ -10,4 +10,4 @@ export { export type { AuthWitnessProvider, ChainInfo } from '@aztec/entrypoints/interfaces'; export { ChainInfoSchema } from '@aztec/entrypoints/interfaces'; -export { SignerlessAccount } from '../account/signerless_account.js'; +export { NO_FROM, type NoFrom } from '../contract/interaction_options.js'; diff --git a/yarn-project/aztec.js/src/authorization/call_authorization_request.ts b/yarn-project/aztec.js/src/authorization/call_authorization_request.ts index d90881a580a7..0b15b70fdde9 100644 --- a/yarn-project/aztec.js/src/authorization/call_authorization_request.ts +++ b/yarn-project/aztec.js/src/authorization/call_authorization_request.ts @@ -22,6 +22,11 @@ export class CallAuthorizationRequest { * poseidon2([msg_sender, selector, args_hash]) */ public innerHash: Fr, + /** + * The address on whose behalf the auth witness should be created. + * This is the account that must sign the authorization. + */ + public onBehalfOf: AztecAddress, /** * The address performing the call */ @@ -75,11 +80,12 @@ export class CallAuthorizationRequest { } const request = new CallAuthorizationRequest( selector, - reader.readField(), - AztecAddress.fromField(reader.readField()), - FunctionSelector.fromField(reader.readField()), - reader.readField(), - reader.readFieldArray(reader.remainingFields()), + reader.readField(), // inner_hash + AztecAddress.fromField(reader.readField()), // on_behalf_of + AztecAddress.fromField(reader.readField()), // msg_sender + FunctionSelector.fromField(reader.readField()), // fn_selector + reader.readField(), // args_hash + reader.readFieldArray(reader.remainingFields()), // args ); await request.validate(); return request; diff --git a/yarn-project/aztec.js/src/contract/batch_call.test.ts b/yarn-project/aztec.js/src/contract/batch_call.test.ts index 88b6e4427d60..32b1b062208c 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.test.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.test.ts @@ -155,14 +155,14 @@ describe('BatchCall', () => { name: 'executeUtility', args: [ expect.objectContaining({ name: 'getBalance', to: contractAddress1 }), - expect.objectContaining({ scope: expect.any(AztecAddress) }), + expect.objectContaining({ scopes: expect.any(Array) }), ], }, { name: 'executeUtility', args: [ expect.objectContaining({ name: 'checkPermission', to: contractAddress3 }), - expect.objectContaining({ scope: expect.any(AztecAddress) }), + expect.objectContaining({ scopes: expect.any(Array) }), ], }, { @@ -220,14 +220,14 @@ describe('BatchCall', () => { name: 'executeUtility', args: [ expect.objectContaining({ name: 'view1', to: contractAddress1 }), - expect.objectContaining({ scope: expect.any(AztecAddress) }), + expect.objectContaining({ scopes: expect.any(Array) }), ], }, { name: 'executeUtility', args: [ expect.objectContaining({ name: 'view2', to: contractAddress2 }), - expect.objectContaining({ scope: expect.any(AztecAddress) }), + expect.objectContaining({ scopes: expect.any(Array) }), ], }, ]); diff --git a/yarn-project/aztec.js/src/contract/batch_call.ts b/yarn-project/aztec.js/src/contract/batch_call.ts index 3012c2dd8f01..3da6dd9ed307 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.ts @@ -4,6 +4,7 @@ import { ExecutionPayload, TxSimulationResult, UtilityExecutionResult, mergeExec import type { BatchedMethod, Wallet } from '../wallet/wallet.js'; import { BaseContractInteraction } from './base_contract_interaction.js'; import { + NO_FROM, type RequestInteractionOptions, type SimulateInteractionOptions, extractOffchainOutput, @@ -78,7 +79,7 @@ export class BatchCall extends BaseContractInteraction { for (const [call] of utility) { batchRequests.push({ name: 'executeUtility' as const, - args: [call, { scope: options.from, authWitnesses: options.authWitnesses }], + args: [call, { scopes: options.from === NO_FROM ? [] : [options.from], authWitnesses: options.authWitnesses }], }); } diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index 252611ee09dd..659561107beb 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -221,7 +221,7 @@ describe('Contract Class', () => { expect(wallet.executeUtility).toHaveBeenCalledTimes(1); expect(wallet.executeUtility).toHaveBeenCalledWith( expect.objectContaining({ name: 'qux', to: contractAddress }), - expect.objectContaining({ scope: account.getAddress() }), + expect.objectContaining({ scopes: [account.getAddress()] }), ); expect(result).toBe(42n); }); diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index db5969932ca6..7359d2c5f618 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -19,6 +19,7 @@ import type { Wallet } from '../wallet/wallet.js'; import { BaseContractInteraction } from './base_contract_interaction.js'; import { getGasLimits } from './get_gas_limits.js'; import { + NO_FROM, type ProfileInteractionOptions, type RequestInteractionOptions, type SimulateInteractionOptions, @@ -130,7 +131,7 @@ export class ContractFunctionInteraction extends BaseContractInteraction { if (this.functionDao.functionType == FunctionType.UTILITY) { const call = await this.getFunctionCall(); const utilityResult = await this.wallet.executeUtility(call, { - scope: options.from, + scopes: options.from === NO_FROM ? [] : [options.from], authWitnesses: options.authWitnesses, }); diff --git a/yarn-project/aztec.js/src/contract/deploy_method.ts b/yarn-project/aztec.js/src/contract/deploy_method.ts index ea3a84f28dda..5e6b278615fc 100644 --- a/yarn-project/aztec.js/src/contract/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_method.ts @@ -20,6 +20,7 @@ import type { ContractBase } from './contract_base.js'; import { ContractFunctionInteraction } from './contract_function_interaction.js'; import { getGasLimits } from './get_gas_limits.js'; import { + NO_FROM, NO_WAIT, type NoWait, type OffchainOutput, @@ -222,10 +223,14 @@ export class DeployMethod extends } convertDeployOptionsToRequestOptions(options: DeployOptionsWithoutWait): RequestDeployOptions { - return { - ...options, - deployer: !options?.universalDeploy ? options.from : undefined, - }; + const { from } = options; + let deployer: AztecAddress | undefined; + if (options?.universalDeploy) { + deployer = undefined; + } else { + deployer = from === NO_FROM ? AztecAddress.ZERO : from; + } + return { ...options, deployer }; } /** diff --git a/yarn-project/aztec.js/src/contract/interaction_options.ts b/yarn-project/aztec.js/src/contract/interaction_options.ts index 27d728ac7228..626381d6f665 100644 --- a/yarn-project/aztec.js/src/contract/interaction_options.ts +++ b/yarn-project/aztec.js/src/contract/interaction_options.ts @@ -74,6 +74,21 @@ export const NO_WAIT = 'NO_WAIT' as const; */ export type NoWait = typeof NO_WAIT; +/** + * Constant for explicitly opting out of account contract mediation. + * When used as the `from` parameter, the wallet executes the payload directly + * via the DefaultEntrypoint without wrapping it in an account contract entrypoint. + * The app is responsible for assembling the complete execution payload, including + * any entrypoint wrapping (e.g. multicall) if needed. This will result in the + * first call of the chain receiving msg_sender as Option::none + */ +export const NO_FROM = 'NO_FROM' as const; + +/** + * Type for the NO_FROM constant. + */ +export type NoFrom = typeof NO_FROM; + /** * Type for wait options in interactions. * - NO_WAIT symbol: Don't wait for confirmation, return TxHash immediately @@ -86,8 +101,8 @@ export type InteractionWaitOptions = NoWait | WaitOpts | undefined; * Base options for calling a (constrained) function in a contract, without wait parameter. */ export type SendInteractionOptionsWithoutWait = RequestInteractionOptions & { - /** The sender's Aztec address. */ - from: AztecAddress; + /** The sender's Aztec address, or NO_FROM to execute without account contract mediation. */ + from: AztecAddress | NoFrom; /** The fee options for the transaction. */ fee?: InteractionFeeOptions; /** diff --git a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts index 61277579681c..aae7f8458a8d 100644 --- a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts @@ -5,6 +5,7 @@ import type { GasSettings } from '@aztec/stdlib/gas'; import { ExecutionPayload } from '@aztec/stdlib/tx'; import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; +import { NO_FROM } from '../contract/interaction_options.js'; import type { Wallet } from '../wallet/wallet.js'; import type { FeePaymentMethod } from './fee_payment_method.js'; @@ -75,11 +76,11 @@ export class PrivateFeePaymentMethod implements FeePaymentMethod { const executionPayload = await interaction.request(); this.assetPromise = this.wallet .simulateTx(executionPayload, { - from: AztecAddress.ZERO, + from: NO_FROM, skipFeeEnforcement: true, }) .then(simulationResult => { - const rawReturnValues = simulationResult.getPrivateReturnValues().nested[0].values; + const rawReturnValues = simulationResult.getPrivateReturnValues().values; return decodeFromAbi(abi.returnTypes, rawReturnValues!); }) as Promise; } diff --git a/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts index 98f20c961241..c7e52fd25413 100644 --- a/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts @@ -5,6 +5,7 @@ import { GasSettings } from '@aztec/stdlib/gas'; import { ExecutionPayload } from '@aztec/stdlib/tx'; import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js'; +import { NO_FROM } from '../contract/interaction_options.js'; import { SetPublicAuthwitContractInteraction } from '../utils/authwit.js'; import type { Wallet } from '../wallet/wallet.js'; import type { FeePaymentMethod } from './fee_payment_method.js'; @@ -69,11 +70,11 @@ export class PublicFeePaymentMethod implements FeePaymentMethod { const executionPayload = await interaction.request(); this.assetPromise = this.wallet .simulateTx(executionPayload, { - from: AztecAddress.ZERO, + from: NO_FROM, skipFeeEnforcement: true, }) .then(simulationResult => { - const rawReturnValues = simulationResult.getPrivateReturnValues().nested[0].values; + const rawReturnValues = simulationResult.getPrivateReturnValues().values; return decodeFromAbi(abi.returnTypes, rawReturnValues!); }) as Promise; } diff --git a/yarn-project/aztec.js/src/wallet/deploy_account_method.ts b/yarn-project/aztec.js/src/wallet/deploy_account_method.ts index 06fa52e36884..73f57bb46c33 100644 --- a/yarn-project/aztec.js/src/wallet/deploy_account_method.ts +++ b/yarn-project/aztec.js/src/wallet/deploy_account_method.ts @@ -1,3 +1,4 @@ +import { DefaultMultiCallEntrypoint } from '@aztec/entrypoints/multicall'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { ContractArtifact, FunctionArtifact } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; @@ -16,10 +17,11 @@ import { type RequestDeployOptions, type SimulateDeployOptions, } from '../contract/deploy_method.js'; -import type { - FeePaymentMethodOption, - InteractionWaitOptions, - ProfileInteractionOptions, +import { + type FeePaymentMethodOption, + type InteractionWaitOptions, + NO_FROM, + type ProfileInteractionOptions, } from '../contract/interaction_options.js'; import type { WaitOpts } from '../contract/wait_opts.js'; import type { FeePaymentMethod } from '../fee/fee_payment_method.js'; @@ -110,6 +112,8 @@ export class DeployAccountMethod exte /** * Returns the execution payload that allows this operation to happen on chain. + * For self-deployments (from === NO_FROM), the payload is wrapped through the + * multicall entrypoint on the app side so the wallet can execute it directly. * @param opts - Configuration options. * @returns The execution payload for this operation */ @@ -137,6 +141,11 @@ export class DeployAccountMethod exte // Notice they are reversed (fee payment usually goes first): // this is because we need to construct the contract BEFORE it can pay for its own fee executionPayloads.push(feeExecutionPayload); + // Wrap the merged payload through the multicall entrypoint, + // producing a single-call payload that DefaultEntrypoint can execute directly. + const multicall = new DefaultMultiCallEntrypoint(); + const chainInfo = await this.wallet.getChainInfo(); + return multicall.wrapExecutionPayload(mergeExecutionPayloads(executionPayloads), chainInfo); } else { const feeExecutionPayload = opts?.fee?.paymentMethod ? await opts.fee.paymentMethod.getExecutionPayload() @@ -144,18 +153,18 @@ export class DeployAccountMethod exte if (feeExecutionPayload) { executionPayloads.unshift(feeExecutionPayload); } + return mergeExecutionPayloads(executionPayloads); } - return mergeExecutionPayloads(executionPayloads); } - override convertDeployOptionsToRequestOptions(options: DeployAccountOptionsWithoutWait): RequestDeployOptions { + override convertDeployOptionsToRequestOptions(options: DeployAccountOptionsWithoutWait): RequestDeployAccountOptions { return { ...options, // Deployer is handled in the request method and forcibly set to undefined, // since our account contracts are created with universalDeployment: true // We need to forward it though, because depending on the deployer we have to assemble // The fee payment method one way or another - deployer: options.from, + deployer: options.from === NO_FROM ? AztecAddress.ZERO : options.from, }; } diff --git a/yarn-project/aztec.js/src/wallet/wallet.test.ts b/yarn-project/aztec.js/src/wallet/wallet.test.ts index 221c2e9e35ac..6ef110d65cd5 100644 --- a/yarn-project/aztec.js/src/wallet/wallet.test.ts +++ b/yarn-project/aztec.js/src/wallet/wallet.test.ts @@ -181,7 +181,7 @@ describe('WalletSchema', () => { returnTypes: [], }); const result = await context.client.executeUtility(call, { - scope: await AztecAddress.random(), + scopes: [await AztecAddress.random()], authWitnesses: [AuthWitness.random()], }); expect(result).toBeInstanceOf(UtilityExecutionResult); @@ -343,7 +343,7 @@ describe('WalletSchema', () => { { name: 'getAccounts', args: [] }, { name: 'registerContract', args: [mockInstance, mockArtifact, undefined] }, { name: 'simulateTx', args: [exec, simulateOpts] }, - { name: 'executeUtility', args: [call, { scope: address3, authWitnesses: [AuthWitness.random()] }] }, + { name: 'executeUtility', args: [call, { scopes: [address3], authWitnesses: [AuthWitness.random()] }] }, { name: 'profileTx', args: [exec, profileOpts] }, { name: 'sendTx', args: [exec, opts] }, { name: 'createAuthWit', args: [address1, { consumer: await AztecAddress.random(), innerHash: Fr.random() }] }, @@ -453,7 +453,7 @@ class MockWallet implements Wallet { executeUtility( _call: any, - _opts: { scope: AztecAddress; authWitnesses?: AuthWitness[] }, + _opts: { scopes: AztecAddress[]; authWitnesses?: AuthWitness[] }, ): Promise { return Promise.resolve(UtilityExecutionResult.random()); } diff --git a/yarn-project/aztec.js/src/wallet/wallet.ts b/yarn-project/aztec.js/src/wallet/wallet.ts index 2c5ebd7e585a..c827b5977106 100644 --- a/yarn-project/aztec.js/src/wallet/wallet.ts +++ b/yarn-project/aztec.js/src/wallet/wallet.ts @@ -33,6 +33,7 @@ import { type FeeEstimationOptions, type GasSettingsOption, type InteractionWaitOptions, + NO_FROM, NO_WAIT, type ProfileInteractionOptions, type SendInteractionOptionsWithoutWait, @@ -235,8 +236,8 @@ export type ContractClassMetadata = { * Options for executing a utility function call. */ export type ExecuteUtilityOptions = { - /** The scope for the utility execution (determines which notes and keys are visible). */ - scope: AztecAddress; + /** The scopes for the utility execution (determines which notes and keys are visible). */ + scopes: AztecAddress[]; /** Optional auth witnesses to use during execution. */ authWitnesses?: AuthWitness[]; }; @@ -303,8 +304,10 @@ export const WaitOptsSchema = z.object({ dontThrowOnRevert: optional(z.boolean()), }); +const FromSchema = z.union([schemas.AztecAddress, z.literal(NO_FROM)]); + export const SendOptionsSchema = z.object({ - from: schemas.AztecAddress, + from: FromSchema, authWitnesses: optional(z.array(AuthWitness.schema)), capsules: optional(z.array(Capsule.schema)), fee: optional(GasSettingsOptionSchema), @@ -313,7 +316,7 @@ export const SendOptionsSchema = z.object({ }); export const SimulateOptionsSchema = z.object({ - from: schemas.AztecAddress, + from: FromSchema, authWitnesses: optional(z.array(AuthWitness.schema)), capsules: optional(z.array(Capsule.schema)), fee: optional(WalletSimulationFeeOptionSchema), @@ -549,7 +552,7 @@ const WalletMethodSchemas = { .args( FunctionCall.schema, z.object({ - scope: schemas.AztecAddress, + scopes: z.array(schemas.AztecAddress), authWitnesses: optional(z.array(AuthWitness.schema)), }), ) diff --git a/yarn-project/bot/src/factory.ts b/yarn-project/bot/src/factory.ts index 99995c4bf8a6..da1da830dc67 100644 --- a/yarn-project/bot/src/factory.ts +++ b/yarn-project/bot/src/factory.ts @@ -1,4 +1,5 @@ import { getInitialTestAccountsData } from '@aztec/accounts/testing'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { BatchCall, @@ -227,7 +228,7 @@ export class BotFactory { await this.withNoMinTxsPerBlock(async () => { const { txHash } = await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { gasSettings, paymentMethod }, wait: NO_WAIT, }); diff --git a/yarn-project/cli-wallet/src/cmds/create_account.ts b/yarn-project/cli-wallet/src/cmds/create_account.ts index 2e317c6a7799..cab5097b0dc9 100644 --- a/yarn-project/cli-wallet/src/cmds/create_account.ts +++ b/yarn-project/cli-wallet/src/cmds/create_account.ts @@ -1,3 +1,4 @@ +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { NO_WAIT } from '@aztec/aztec.js/contracts'; import type { AztecNode } from '@aztec/aztec.js/node'; @@ -72,7 +73,7 @@ export async function createAccount( const { paymentMethod, gasSettings } = await feeOpts.toUserFeeOptions(aztecNode, wallet, address); const delegatedDeployment = deployer && !account.address.equals(deployer); - const from = delegatedDeployment ? deployer : AztecAddress.ZERO; + const from = delegatedDeployment ? deployer : NO_FROM; const deployAccountOpts: DeployAccountOptions = { skipClassPublication: !registerClass, diff --git a/yarn-project/cli-wallet/src/cmds/deploy.ts b/yarn-project/cli-wallet/src/cmds/deploy.ts index edc7e5db29a9..02ab137786b3 100644 --- a/yarn-project/cli-wallet/src/cmds/deploy.ts +++ b/yarn-project/cli-wallet/src/cmds/deploy.ts @@ -1,3 +1,4 @@ +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import type { DeployOptions } from '@aztec/aztec.js/contracts'; import { NO_WAIT } from '@aztec/aztec.js/contracts'; @@ -63,7 +64,7 @@ export async function deploy( const { paymentMethod, gasSettings } = await feeOpts.toUserFeeOptions(node, wallet, deployer); const deployOpts: DeployOptions = { fee: { gasSettings, paymentMethod }, - from: deployer ?? AztecAddress.ZERO, + from: deployer ?? NO_FROM, contractAddressSalt: salt, universalDeploy: !deployer, skipClassPublication, diff --git a/yarn-project/cli-wallet/src/cmds/deploy_account.ts b/yarn-project/cli-wallet/src/cmds/deploy_account.ts index 4667fa6f12c6..95974a620b98 100644 --- a/yarn-project/cli-wallet/src/cmds/deploy_account.ts +++ b/yarn-project/cli-wallet/src/cmds/deploy_account.ts @@ -1,3 +1,4 @@ +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { NO_WAIT } from '@aztec/aztec.js/contracts'; import type { AztecNode } from '@aztec/aztec.js/node'; @@ -52,7 +53,7 @@ export async function deployAccount( const { paymentMethod, gasSettings } = await feeOpts.toUserFeeOptions(aztecNode, wallet, address); const delegatedDeployment = deployer && !account.address.equals(deployer); - const from = delegatedDeployment ? deployer : AztecAddress.ZERO; + const from = delegatedDeployment ? deployer : NO_FROM; const deployAccountOpts: DeployAccountOptions = { skipClassPublication: !registerClass, diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index 3cdb6f9fead1..3768dc272267 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -2,7 +2,7 @@ import { EcdsaRAccountContract, EcdsaRSSHAccountContract } from '@aztec/accounts import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; import { StubAccountContractArtifact, createStubAccount } from '@aztec/accounts/stub'; import { getIdentities } from '@aztec/accounts/utils'; -import { type Account, type AccountContract, SignerlessAccount } from '@aztec/aztec.js/account'; +import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import { type InteractionFeeOptions, getContractInstanceFromInstantiationParams, @@ -11,6 +11,7 @@ import { import type { AztecNode } from '@aztec/aztec.js/node'; import { AccountManager, type Aliased, type SimulateOptions } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; +import { DefaultEntrypoint } from '@aztec/entrypoints/default'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { LogFn } from '@aztec/foundation/log'; import type { NotesFilter } from '@aztec/pxe/client/lazy'; @@ -20,7 +21,7 @@ import { createPXE, getPXEConfig } from '@aztec/pxe/server'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { NoteDao } from '@aztec/stdlib/note'; -import type { SimulationOverrides, TxProvingResult, TxSimulationResult } from '@aztec/stdlib/tx'; +import type { SimulationOverrides, TxExecutionRequest, TxProvingResult, TxSimulationResult } from '@aztec/stdlib/tx'; import { ExecutionPayload, mergeExecutionPayloads } from '@aztec/stdlib/tx'; import { BaseWallet, type SimulateViaEntrypointOptions } from '@aztec/wallet-sdk/base-wallet'; @@ -71,7 +72,8 @@ export class CLIWallet extends BaseWallet { const executionOptions: DefaultAccountEntrypointOptions = { txNonce, cancellable: this.cancellableTransactions, - feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions, + // If from is an address, feeOptions include the way the account contract should handle the fee payment + feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions!, }; return await fromAccount.createTxExecutionRequest( feeExecutionPayload ?? executionPayload, @@ -92,9 +94,7 @@ export class CLIWallet extends BaseWallet { override async getAccountFromAddress(address: AztecAddress) { let account: Account | undefined; - if (address.equals(AztecAddress.ZERO)) { - account = new SignerlessAccount(); - } else if (this.accountCache.has(address.toString())) { + if (this.accountCache.has(address.toString())) { return this.accountCache.get(address.toString())!; } else { const accountManager = await this.createOrRetrieveAccount(address); @@ -185,13 +185,7 @@ export class CLIWallet extends BaseWallet { */ private async getFakeAccountDataFor(address: AztecAddress) { const originalAccount = await this.getAccountFromAddress(address); - // Account contracts can only be overridden if they have an associated address - // Overwriting SignerlessAccount is not supported, and does not really make sense - // since it has no authorization mechanism. - if (originalAccount instanceof SignerlessAccount) { - throw new Error(`Cannot create fake account data for SignerlessAccount at address: ${address}`); - } - const originalAddress = (originalAccount as Account).getCompleteAddress(); + const originalAddress = originalAccount.getCompleteAddress(); const contractInstance = await this.pxe.getContractInstance(originalAddress.address); if (!contractInstance) { throw new Error(`No contract instance found for address: ${originalAddress.address}`); @@ -220,42 +214,43 @@ export class CLIWallet extends BaseWallet { /** * Uses a stub account for kernelless simulation, bypassing real account authorization. - * Falls through to the standard entrypoint path for SignerlessAccount (ZERO address). + * Uses DefaultEntrypoint directly for NO_FROM transactions. */ protected override async simulateViaEntrypoint( executionPayload: ExecutionPayload, opts: SimulateViaEntrypointOptions, ): Promise { const { from, feeOptions, scopes } = opts; + const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); + const finalExecutionPayload = feeExecutionPayload + ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) + : executionPayload; + const chainInfo = await this.getChainInfo(); + let overrides: SimulationOverrides | undefined; - let fromAccount: Account; - if (!from.equals(AztecAddress.ZERO)) { + let txRequest: TxExecutionRequest; + if (from === NO_FROM) { + const entrypoint = new DefaultEntrypoint(); + txRequest = await entrypoint.createTxExecutionRequest(finalExecutionPayload, feeOptions.gasSettings, chainInfo); + } else { const { account, instance, artifact } = await this.getFakeAccountDataFor(from); - fromAccount = account; overrides = { contracts: { [from.toString()]: { instance, artifact } }, }; - } else { - fromAccount = await this.getAccountFromAddress(from); + const executionOptions: DefaultAccountEntrypointOptions = { + txNonce: Fr.random(), + cancellable: this.cancellableTransactions, + // If from is an address, feeOptions include the way the account contract should handle the fee payment + feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions!, + }; + txRequest = await account.createTxExecutionRequest( + finalExecutionPayload, + feeOptions.gasSettings, + chainInfo, + executionOptions, + ); } - const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); - const executionOptions: DefaultAccountEntrypointOptions = { - txNonce: Fr.random(), - cancellable: this.cancellableTransactions, - feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions, - }; - const finalExecutionPayload = feeExecutionPayload - ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) - : executionPayload; - - const chainInfo = await this.getChainInfo(); - const txRequest = await fromAccount.createTxExecutionRequest( - finalExecutionPayload, - feeOptions.gasSettings, - chainInfo, - executionOptions, - ); return this.pxe.simulateTx(txRequest, { simulatePublic: true, skipFeeEnforcement: true, diff --git a/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts b/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts index e38eb09f7c9f..ef092df34035 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/account_deployments.test.ts @@ -1,4 +1,5 @@ import { EcdsaRAccountContractArtifact } from '@aztec/accounts/ecdsa'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { publishContractClass } from '@aztec/aztec.js/deployment'; import type { DeployAccountOptions, Wallet } from '@aztec/aztec.js/wallet'; @@ -65,7 +66,7 @@ describe('Deployment benchmark', () => { // Publicly deploy the contract, but skip the class registration as that is the // "typical" use case const options: DeployAccountOptions = { - from: AztecAddress.ZERO, // Self deployment + from: NO_FROM, // Self deployment skipClassPublication: true, skipInstancePublication: false, skipInitialization: false, diff --git a/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts b/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts index 0e19339412fd..5f8a9b8ec908 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts @@ -1,3 +1,4 @@ +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { FeeJuicePaymentMethodWithClaim } from '@aztec/aztec.js/fee'; import { type FeePaymentMethod, PrivateFeePaymentMethod, SponsoredFeePaymentMethod } from '@aztec/aztec.js/fee'; @@ -333,7 +334,7 @@ export class ClientFlowsBenchmark { const claim = await this.feeJuiceBridgeTestHarness.prepareTokensOnL1(benchysAddress); const behchysDeployMethod = await benchysAccountManager.getDeployMethod(); await behchysDeployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: new FeeJuicePaymentMethodWithClaim(benchysAddress, claim) }, }); // Register benchy on the user's Wallet, where we're going to be interacting from diff --git a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts index cc5beea141ba..eb779d1c82a0 100644 --- a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts +++ b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts @@ -6,6 +6,7 @@ * attestations are signed, and no double-signing occurs. */ import { type AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; import { waitForProven } from '@aztec/aztec.js/contracts'; import { ContractDeployer } from '@aztec/aztec.js/deployment'; @@ -255,7 +256,7 @@ describe('HA Full Setup', () => { accountData.signingKey, ); const deployMethod = await accountManager.getDeployMethod(); - await deployMethod.send({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: NO_FROM }); ownerAddress = accountManager.address; logger.info(`Test account deployed at ${ownerAddress}`); }); diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index 41e9a7e20d11..fd4ee33f95bc 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -1,4 +1,5 @@ import type { InitialAccountData } from '@aztec/accounts/testing'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { Fr } from '@aztec/aztec.js/fields'; import type { Logger } from '@aztec/aztec.js/log'; @@ -39,7 +40,7 @@ describe('e2e_2_pxes', () => { fundedAccounts[accountIndex].salt, ); const deployMethod = await accountManager.getDeployMethod(); - await deployMethod.send({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: NO_FROM }); return { wallet, address: accountManager.address, teardown }; } @@ -192,7 +193,7 @@ describe('e2e_2_pxes', () => { const sharedAccount = initialFundedAccounts[2]; const sharedAccountOnAManager = await walletA.createSchnorrAccount(sharedAccount.secret, sharedAccount.salt); const sharedAccountOnADeployMethod = await sharedAccountOnAManager.getDeployMethod(); - await sharedAccountOnADeployMethod.send({ from: AztecAddress.ZERO }); + await sharedAccountOnADeployMethod.send({ from: NO_FROM }); const sharedAccountAddress = sharedAccountOnAManager.address; // Register the shared account on walletB. diff --git a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts index 185d4ca73cb4..f501ec18c8ef 100644 --- a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts +++ b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts @@ -1,6 +1,12 @@ import { EcdsaKAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { type Account, type AccountContract, BaseAccount, getAccountContractAddress } from '@aztec/aztec.js/account'; +import { + type Account, + type AccountContract, + BaseAccount, + NO_FROM, + getAccountContractAddress, +} from '@aztec/aztec.js/account'; import { AztecAddress, CompleteAddress } from '@aztec/aztec.js/addresses'; import { Fr, GrumpkinScalar } from '@aztec/aztec.js/fields'; import type { Logger } from '@aztec/aztec.js/log'; @@ -61,7 +67,7 @@ const itShouldBehaveLikeAnAccountContract = ( if (await accountManager.hasInitializer()) { // The account is pre-funded and can pay for its own fee. const deployMethod = await accountManager.getDeployMethod(); - await deployMethod.send({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: NO_FROM }); } ({ contract: child } = await ChildContract.deploy(wallet).send({ from: address })); diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index f0610d6a5e79..bef140aaad11 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -1,3 +1,4 @@ +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { BatchCall, ContractFunctionInteraction, type DeployOptions, NO_WAIT } from '@aztec/aztec.js/contracts'; import { Fr } from '@aztec/aztec.js/fields'; @@ -507,7 +508,7 @@ describe('e2e_block_building', () => { const accountManager = await (wallet as TestWallet).createSchnorrAccount(accountData.secret, accountData.salt); const deployMethod = await accountManager.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, }); }); diff --git a/yarn-project/end-to-end/src/e2e_bot.test.ts b/yarn-project/end-to-end/src/e2e_bot.test.ts index ba55a1a4941d..f5e02398f6de 100644 --- a/yarn-project/end-to-end/src/e2e_bot.test.ts +++ b/yarn-project/end-to-end/src/e2e_bot.test.ts @@ -1,4 +1,5 @@ import { getInitialTestAccountsData } from '@aztec/accounts/testing'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { Fr } from '@aztec/aztec.js/fields'; import type { AztecNode } from '@aztec/aztec.js/node'; import { TxReceipt } from '@aztec/aztec.js/tx'; @@ -17,7 +18,6 @@ import { MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS } from '@ import { SecretValue } from '@aztec/foundation/config'; import { bufferToHex } from '@aztec/foundation/string'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; @@ -47,7 +47,7 @@ describe('e2e_bot', () => { wallet = await EmbeddedWallet.create(aztecNode, { ephemeral: true }); const accountManager = await wallet.createSchnorrAccount(botAccount.secret, botAccount.salt, botAccount.signingKey); const deployMethod = await accountManager.getDeployMethod(); - await deployMethod.send({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: NO_FROM }); }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts index a02e539963db..bf2bc79020cd 100644 --- a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts @@ -1,4 +1,5 @@ import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { CompleteAddress } from '@aztec/aztec.js/addresses'; import { FeeJuicePaymentMethodWithClaim, PrivateFeePaymentMethod, PublicFeePaymentMethod } from '@aztec/aztec.js/fee'; import { Fr } from '@aztec/aztec.js/fields'; @@ -89,7 +90,7 @@ describe('e2e_fees account_init', () => { const [bobsInitialGas] = await t.getGasBalanceFn(bobsAddress); expect(bobsInitialGas).toEqual(mintAmount); - const { receipt: tx } = await bobsDeployMethod.send({ from: AztecAddress.ZERO, wait: { returnReceipt: true } }); + const { receipt: tx } = await bobsDeployMethod.send({ from: NO_FROM, wait: { returnReceipt: true } }); expect(tx.transactionFee!).toBeGreaterThan(0n); await expect(t.getGasBalanceFn(bobsAddress)).resolves.toEqual([bobsInitialGas - tx.transactionFee!]); @@ -99,7 +100,7 @@ describe('e2e_fees account_init', () => { const claim = await t.feeJuiceBridgeTestHarness.prepareTokensOnL1(bobsAddress); const paymentMethod = new FeeJuicePaymentMethodWithClaim(bobsAddress, claim); const { receipt: tx } = await bobsDeployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod }, wait: { returnReceipt: true }, }); @@ -119,7 +120,7 @@ describe('e2e_fees account_init', () => { const gasSettings = GasSettings.default({ maxFeesPerGas }); const paymentMethod = new PrivateFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings); const { receipt: tx } = await bobsDeployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod }, wait: { returnReceipt: true }, }); @@ -148,7 +149,7 @@ describe('e2e_fees account_init', () => { const gasSettings = GasSettings.default({ maxFeesPerGas }); const paymentMethod = new PublicFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings); const { receipt: tx } = await bobsDeployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, skipInstancePublication: false, fee: { paymentMethod }, wait: { returnReceipt: true }, diff --git a/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts b/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts index 683bcbb3fb32..13d359ff5b46 100644 --- a/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts +++ b/yarn-project/end-to-end/src/e2e_kernelless_simulation.test.ts @@ -125,14 +125,18 @@ describe('Kernelless simulation', () => { expect(token0AuthwitRequest.contractAddress).toEqual(token0.address); expect(token1AuthwitRequest.contractAddress).toEqual(token1.address); - // Authwit selector + inner_hash + msg_sender + function_selector + args_hash + args (4) - expect(token0AuthwitRequest.data).toHaveLength(9); - expect(token1AuthwitRequest.data).toHaveLength(9); + // Authwit selector + inner_hash + on_behalf_of + msg_sender + function_selector + args_hash + args (4) + expect(token0AuthwitRequest.data).toHaveLength(10); + expect(token1AuthwitRequest.data).toHaveLength(10); const token0CallAuthorizationRequest = await CallAuthorizationRequest.fromFields(token0AuthwitRequest.data); const token1CallAuthorizationRequest = await CallAuthorizationRequest.fromFields(token1AuthwitRequest.data); expect(token0CallAuthorizationRequest.selector).toEqual(token1CallAuthorizationRequest.selector); + expect(token0CallAuthorizationRequest.onBehalfOf).toEqual(liquidityProviderAddress); + expect(token1CallAuthorizationRequest.onBehalfOf).toEqual(liquidityProviderAddress); + expect(token0CallAuthorizationRequest.msgSender).toEqual(amm.address); + expect(token1CallAuthorizationRequest.msgSender).toEqual(amm.address); const functionAbi = await getFunctionArtifact( TokenContractArtifact, @@ -342,14 +346,18 @@ describe('Kernelless simulation', () => { from: adminAddress, }); - // Kernelless simulation of reading + nullifying that settled note produces a settled - // read request that gets verified against the note hash tree at the anchor block + // Spy on the node API that generateSimulatedProvingResult uses to verify settled read requests + const noteHashMembershipWitnessSpy = jest.spyOn(aztecNode, 'getNoteHashMembershipWitness'); + wallet.setSimulationMode('kernelless-override'); await expect( pendingNoteHashesContract.methods.get_then_nullify_note(mintAmount, adminAddress).simulate({ from: adminAddress, }), ).resolves.toBeDefined(); + + expect(noteHashMembershipWitnessSpy).toHaveBeenCalled(); + noteHashMembershipWitnessSpy.mockRestore(); }); }); }); diff --git a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts index 2b3d00198fa4..c885c8a4745c 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts @@ -1,6 +1,7 @@ import { type InitialAccountData, getInitialTestAccountsData } from '@aztec/accounts/testing'; import type { AztecNodeService } from '@aztec/aztec-node'; -import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; +import { NO_FROM } from '@aztec/aztec.js/account'; +import { EthAddress } from '@aztec/aztec.js/addresses'; import { waitForProven } from '@aztec/aztec.js/contracts'; import { generateClaimSecret } from '@aztec/aztec.js/ethereum'; import { Fr } from '@aztec/aztec.js/fields'; @@ -280,7 +281,7 @@ describe('e2e_p2p_add_rollup', () => { const aliceAccountManager = await wallet.createSchnorrAccount(aliceAccount.secret, aliceAccount.salt); const aliceDeploymethod = await aliceAccountManager.getDeployMethod(); await aliceDeploymethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, }); const aliceAddress = aliceAccountManager.address; diff --git a/yarn-project/end-to-end/src/e2e_sequencer_config.test.ts b/yarn-project/end-to-end/src/e2e_sequencer_config.test.ts index a0c91687d866..532861cb9d27 100644 --- a/yarn-project/end-to-end/src/e2e_sequencer_config.test.ts +++ b/yarn-project/end-to-end/src/e2e_sequencer_config.test.ts @@ -1,4 +1,5 @@ import { getInitialTestAccountsData } from '@aztec/accounts/testing'; +import { NO_FROM } from '@aztec/aztec.js/account'; import type { AztecNode } from '@aztec/aztec.js/node'; import { TxReceipt } from '@aztec/aztec.js/tx'; import { Bot, type BotConfig, BotStore, getBotDefaultConfig } from '@aztec/bot'; @@ -6,7 +7,6 @@ import { DEFAULT_DA_GAS_LIMIT } from '@aztec/constants'; import type { Logger } from '@aztec/foundation/log'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import type { SequencerClient } from '@aztec/sequencer-client'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; import { jest } from '@jest/globals'; @@ -52,7 +52,7 @@ describe('e2e_sequencer_config', () => { botAccount.signingKey, ); const deployMethod = await accountManager.getDeployMethod(); - await deployMethod.send({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: NO_FROM }); bot = await Bot.create(config, wallet, aztecNode, undefined, new BotStore(await openTmpStore('bot'))); }); diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index c93560f43064..e03737b2669a 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -34,6 +34,7 @@ import type { InitialAccountData } from '@aztec/accounts/testing'; import { createArchiver } from '@aztec/archiver'; import { AztecNodeService } from '@aztec/aztec-node'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { BatchCall, type Contract, NO_WAIT } from '@aztec/aztec.js/contracts'; import { Fr, GrumpkinScalar } from '@aztec/aztec.js/fields'; import { type Logger, createLogger } from '@aztec/aztec.js/log'; @@ -153,7 +154,7 @@ class TestVariant { await Promise.all( managers.map(async m => { const deployMethod = await m.getDeployMethod(); - return deployMethod.send({ from: AztecAddress.ZERO }); + return deployMethod.send({ from: NO_FROM }); }), ); return accounts.map(acc => acc.address); diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index 059c7c295899..94843ea53a82 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -1,6 +1,7 @@ import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr'; import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; import { BatchCall, @@ -847,7 +848,7 @@ export const deployAccounts = ); const deployMethod = await accountManager.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, skipClassPublication: i !== 0, // Publish the contract class at most once. }); } diff --git a/yarn-project/end-to-end/src/spartan/block_capacity.test.ts b/yarn-project/end-to-end/src/spartan/block_capacity.test.ts index 308d84285c9a..417d63a4b902 100644 --- a/yarn-project/end-to-end/src/spartan/block_capacity.test.ts +++ b/yarn-project/end-to-end/src/spartan/block_capacity.test.ts @@ -1,4 +1,5 @@ import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { type ContractFunctionInteraction, NO_WAIT, toSendOptions } from '@aztec/aztec.js/contracts'; import { SponsoredFeePaymentMethod } from '@aztec/aztec.js/fee'; @@ -114,7 +115,7 @@ describe('block capacity benchmark', () => { ); const deployMethod = await manager.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: sponsor }, wait: { timeout: 2400 }, }); diff --git a/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts b/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts index 48986bd06c53..f3a69bcbf4b4 100644 --- a/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts +++ b/yarn-project/end-to-end/src/spartan/n_tps_prove.test.ts @@ -1,4 +1,5 @@ import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { toSendOptions } from '@aztec/aztec.js/contracts'; import { SponsoredFeePaymentMethod } from '@aztec/aztec.js/fee'; @@ -317,7 +318,7 @@ describe(`prove ${TARGET_TPS}TPS test`, () => { ); const deployMethod = await manager.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod: sponsor }, wait: { timeout: 2400 }, }); diff --git a/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts b/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts index 814ba7fb747a..6827bd11fd92 100644 --- a/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts +++ b/yarn-project/end-to-end/src/spartan/setup_test_wallets.ts @@ -1,4 +1,5 @@ import { generateSchnorrAccounts } from '@aztec/accounts/testing'; +import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { NO_WAIT } from '@aztec/aztec.js/contracts'; import { L1FeeJuicePortalManager } from '@aztec/aztec.js/ethereum'; @@ -89,7 +90,7 @@ export async function deploySponsoredTestAccountsWithTokens( const paymentMethod = new SponsoredFeePaymentMethod(await getSponsoredFPCAddress()); const recipientDeployMethod = await recipientAccount.getDeployMethod(); await recipientDeployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod }, wait: { timeout: 2400 }, }); @@ -97,7 +98,7 @@ export async function deploySponsoredTestAccountsWithTokens( fundedAccounts.map(async a => { const deployMethod = await a.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod }, wait: { timeout: 2400 }, }); // increase timeout on purpose in order to account for two empty epochs @@ -141,12 +142,12 @@ async function deployAccountWithDiagnostics( try { let gasSettings; if (estimateGas) { - const sim = await deployMethod.simulate({ from: AztecAddress.ZERO, fee: { paymentMethod } }); + const sim = await deployMethod.simulate({ from: NO_FROM, fee: { paymentMethod } }); gasSettings = sim.estimatedGas; logger.info(`${accountLabel} estimated gas: DA=${gasSettings.gasLimits.daGas} L2=${gasSettings.gasLimits.l2Gas}`); } const deployResult = await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, fee: { paymentMethod, gasSettings }, wait: NO_WAIT, }); @@ -269,7 +270,7 @@ export async function deployTestAccountsWithTokens( fundedAccounts.map(async (a, i) => { const paymentMethod = new FeeJuicePaymentMethodWithClaim(a.address, claims[i]); const deployMethod = await a.getDeployMethod(); - await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod } }); + await deployMethod.send({ from: NO_FROM, fee: { paymentMethod } }); logger.info(`Account deployed at ${a.address}`); }), ); diff --git a/yarn-project/end-to-end/src/test-wallet/test_wallet.ts b/yarn-project/end-to-end/src/test-wallet/test_wallet.ts index e1082cdc1a14..1352891597aa 100644 --- a/yarn-project/end-to-end/src/test-wallet/test_wallet.ts +++ b/yarn-project/end-to-end/src/test-wallet/test_wallet.ts @@ -1,7 +1,7 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; import { StubAccountContractArtifact, createStubAccount } from '@aztec/accounts/stub'; -import { type Account, type AccountContract, SignerlessAccount } from '@aztec/aztec.js/account'; +import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import { type CallIntent, type ContractFunctionInteractionCallIntent, @@ -14,6 +14,7 @@ import { import type { AztecNode } from '@aztec/aztec.js/node'; import { AccountManager, type SendOptions } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; +import { DefaultEntrypoint } from '@aztec/entrypoints/default'; import { Fq, Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { NotesFilter } from '@aztec/pxe/client/lazy'; @@ -24,7 +25,14 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import type { NoteDao } from '@aztec/stdlib/note'; -import type { BlockHeader, SimulationOverrides, TxHash, TxReceipt, TxSimulationResult } from '@aztec/stdlib/tx'; +import type { + BlockHeader, + SimulationOverrides, + TxExecutionRequest, + TxHash, + TxReceipt, + TxSimulationResult, +} from '@aztec/stdlib/tx'; import { ExecutionPayload, mergeExecutionPayloads } from '@aztec/stdlib/tx'; import { BaseWallet, type SimulateViaEntrypointOptions } from '@aztec/wallet-sdk/base-wallet'; @@ -104,10 +112,7 @@ export class TestWallet extends BaseWallet { async getFakeAccountDataFor(address: AztecAddress) { const originalAccount = await this.getAccountFromAddress(address); - if (originalAccount instanceof SignerlessAccount) { - throw new Error(`Cannot create fake account data for SignerlessAccount at address: ${address}`); - } - const originalAddress = (originalAccount as Account).getCompleteAddress(); + const originalAddress = originalAccount.getCompleteAddress(); const contractInstance = await this.pxe.getContractInstance(originalAddress.address); if (!contractInstance) { throw new Error(`No contract instance found for address: ${originalAddress.address}`); @@ -141,12 +146,7 @@ export class TestWallet extends BaseWallet { } protected getAccountFromAddress(address: AztecAddress): Promise { - let account: Account | undefined; - if (address.equals(AztecAddress.ZERO)) { - account = new SignerlessAccount(); - } else { - account = this.accounts.get(address?.toString() ?? ''); - } + const account = this.accounts.get(address?.toString() ?? ''); if (!account) { throw new Error(`Account not found in wallet for address: ${address}`); @@ -220,36 +220,43 @@ export class TestWallet extends BaseWallet { ): Promise { const { from, feeOptions, scopes, skipTxValidation, skipFeeEnforcement } = opts; const skipKernels = this.simulationMode !== 'full'; - const useOverride = this.simulationMode === 'kernelless-override' && !from.equals(AztecAddress.ZERO); - - let overrides: SimulationOverrides | undefined; - let fromAccount: Account; - if (useOverride) { - const { account, instance, artifact } = await this.getFakeAccountDataFor(from); - fromAccount = account; - overrides = { - contracts: { [from.toString()]: { instance, artifact } }, - }; - } else { - fromAccount = await this.getAccountFromAddress(from); - } - const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); - const executionOptions: DefaultAccountEntrypointOptions = { - txNonce: Fr.random(), - cancellable: this.cancellableTransactions, - feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions, - }; const finalExecutionPayload = feeExecutionPayload ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) : executionPayload; const chainInfo = await this.getChainInfo(); - const txRequest = await fromAccount.createTxExecutionRequest( - finalExecutionPayload, - feeOptions.gasSettings, - chainInfo, - executionOptions, - ); + + let overrides: SimulationOverrides | undefined; + let txRequest: TxExecutionRequest; + if (from === NO_FROM) { + const entrypoint = new DefaultEntrypoint(); + txRequest = await entrypoint.createTxExecutionRequest(finalExecutionPayload, feeOptions.gasSettings, chainInfo); + } else { + const useOverride = this.simulationMode === 'kernelless-override'; + let fromAccount: Account; + if (useOverride) { + const { account, instance, artifact } = await this.getFakeAccountDataFor(from); + fromAccount = account; + overrides = { + contracts: { [from.toString()]: { instance, artifact } }, + }; + } else { + fromAccount = await this.getAccountFromAddress(from); + } + const executionOptions: DefaultAccountEntrypointOptions = { + txNonce: Fr.random(), + cancellable: this.cancellableTransactions, + // If from is an address, feeOptions include the way the account contract should handle the fee payment + feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions!, + }; + txRequest = await fromAccount.createTxExecutionRequest( + finalExecutionPayload, + feeOptions.gasSettings, + chainInfo, + executionOptions, + ); + } + return this.pxe.simulateTx(txRequest, { simulatePublic: true, skipKernels, diff --git a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts index 762d520c156e..4b876b9aa659 100644 --- a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts +++ b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts @@ -1,4 +1,5 @@ -import type { Account } from '@aztec/aztec.js/account'; +import type { Account, NoFrom } from '@aztec/aztec.js/account'; +import { NO_FROM } from '@aztec/aztec.js/account'; import type { CallIntent, IntentInnerHash } from '@aztec/aztec.js/authorization'; import { type InteractionWaitOptions, @@ -29,6 +30,7 @@ import { GAS_ESTIMATION_TEARDOWN_L2_GAS_LIMIT, } from '@aztec/constants'; import { AccountFeePaymentMethodOptions, type DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; +import { DefaultEntrypoint } from '@aztec/entrypoints/default'; import type { ChainInfo } from '@aztec/entrypoints/interfaces'; import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; @@ -75,7 +77,7 @@ export type FeeOptions = { */ walletFeePaymentMethod?: FeePaymentMethod; /** Configuration options for the account to properly handle the selected fee payment method */ - accountFeePaymentMethodOptions: AccountFeePaymentMethodOptions; + accountFeePaymentMethodOptions?: AccountFeePaymentMethodOptions; /** The gas settings to use for the transaction */ gasSettings: GasSettings; }; @@ -104,8 +106,8 @@ export abstract class BaseWallet implements Wallet { protected log = createLogger('wallet-sdk:base_wallet'), ) {} - protected scopesFrom(from: AztecAddress, additionalScopes: AztecAddress[] = []): AztecAddress[] { - const allScopes = from.isZero() ? additionalScopes : [from, ...additionalScopes]; + protected scopesFrom(from: AztecAddress | NoFrom, additionalScopes: AztecAddress[] = []): AztecAddress[] { + const allScopes = from === NO_FROM ? additionalScopes : [from, ...additionalScopes]; const scopeSet = new Set(allScopes.map(address => address.toString())); return [...scopeSet].map(AztecAddress.fromString); } @@ -133,26 +135,33 @@ export abstract class BaseWallet implements Wallet { protected async createTxExecutionRequestFromPayloadAndFee( executionPayload: ExecutionPayload, - from: AztecAddress, + from: AztecAddress | NoFrom, feeOptions: FeeOptions, ): Promise { const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); - const executionOptions: DefaultAccountEntrypointOptions = { - txNonce: Fr.random(), - cancellable: this.cancellableTransactions, - feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions, - }; const finalExecutionPayload = feeExecutionPayload ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) : executionPayload; - const fromAccount = await this.getAccountFromAddress(from); const chainInfo = await this.getChainInfo(); - return fromAccount.createTxExecutionRequest( - finalExecutionPayload, - feeOptions.gasSettings, - chainInfo, - executionOptions, - ); + + if (from === NO_FROM) { + const entrypoint = new DefaultEntrypoint(); + return entrypoint.createTxExecutionRequest(finalExecutionPayload, feeOptions.gasSettings, chainInfo); + } else { + const fromAccount = await this.getAccountFromAddress(from); + const executionOptions: DefaultAccountEntrypointOptions = { + txNonce: Fr.random(), + cancellable: this.cancellableTransactions, + // If from is an address, feeOptions include the way the account contract should handle the fee payment + feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions!, + }; + return fromAccount.createTxExecutionRequest( + finalExecutionPayload, + feeOptions.gasSettings, + chainInfo, + executionOptions, + ); + } } public async createAuthWit( @@ -207,23 +216,27 @@ export abstract class BaseWallet implements Wallet { * @returns - Complete fee options that can be used to create a transaction execution request */ protected async completeFeeOptions( - from: AztecAddress, + from: AztecAddress | NoFrom, feePayer?: AztecAddress, gasSettings?: Partial>, ): Promise { const maxFeesPerGas = gasSettings?.maxFeesPerGas ?? (await this.aztecNode.getCurrentMinFees()).mul(1 + this.minFeePadding); let accountFeePaymentMethodOptions; - // The transaction does not include a fee payment method, so we set the flag - // for the account to use its fee juice balance - if (!feePayer) { - accountFeePaymentMethodOptions = AccountFeePaymentMethodOptions.PREEXISTING_FEE_JUICE; - } else { - // The transaction includes fee payment method, so we check if we are the fee payer for it - // (this can only happen if the embedded payment method is FeeJuiceWithClaim) - accountFeePaymentMethodOptions = from.equals(feePayer) - ? AccountFeePaymentMethodOptions.FEE_JUICE_WITH_CLAIM - : AccountFeePaymentMethodOptions.EXTERNAL; + // If from is an address, we need to determine the appropriate fee payment method options for the + // account contract entrypoint to use + if (from !== NO_FROM) { + if (!feePayer) { + // The transaction does not include a fee payment method, so we set the flag + // for the account to use its fee juice balance + accountFeePaymentMethodOptions = AccountFeePaymentMethodOptions.PREEXISTING_FEE_JUICE; + } else { + // The transaction includes fee payment method, so we check if we are the fee payer for it + // (this can only happen if the embedded payment method is FeeJuiceWithClaim) + accountFeePaymentMethodOptions = from.equals(feePayer) + ? AccountFeePaymentMethodOptions.FEE_JUICE_WITH_CLAIM + : AccountFeePaymentMethodOptions.EXTERNAL; + } } const fullGasSettings: GasSettings = GasSettings.default({ ...gasSettings, maxFeesPerGas }); this.log.debug(`Using L2 gas settings`, fullGasSettings); @@ -243,7 +256,7 @@ export abstract class BaseWallet implements Wallet { * @param gasSettings - User-provided partial gas settings */ protected async completeFeeOptionsForEstimation( - from: AztecAddress, + from: AztecAddress | NoFrom, feePayer?: AztecAddress, gasSettings?: Partial>, ) { @@ -351,12 +364,13 @@ export abstract class BaseWallet implements Wallet { blockHeader = (await this.aztecNode.getBlockHeader())!; } + const simulationOrigin = opts.from === NO_FROM ? AztecAddress.ZERO : opts.from; const [optimizedResults, normalResult] = await Promise.all([ optimizableCalls.length > 0 ? simulateViaNode( this.aztecNode, optimizableCalls, - opts.from, + simulationOrigin, chainInfo, feeOptions.gasSettings, blockHeader, @@ -455,7 +469,7 @@ export abstract class BaseWallet implements Wallet { } executeUtility(call: FunctionCall, opts: ExecuteUtilityOptions): Promise { - return this.pxe.executeUtility(call, { authwits: opts.authWitnesses, scopes: [opts.scope] }); + return this.pxe.executeUtility(call, { authwits: opts.authWitnesses, scopes: opts.scopes }); } async getPrivateEvents( diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index dbc63b3deef8..8b8bf04d6f6d 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -1,9 +1,10 @@ -import { type Account, SignerlessAccount } from '@aztec/aztec.js/account'; +import { type Account, NO_FROM } from '@aztec/aztec.js/account'; import { CallAuthorizationRequest } from '@aztec/aztec.js/authorization'; import { type InteractionWaitOptions, type SendReturn, getGasLimits } from '@aztec/aztec.js/contracts'; import type { Aliased, SendOptions } from '@aztec/aztec.js/wallet'; import { AccountManager } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; +import { DefaultEntrypoint } from '@aztec/entrypoints/default'; import { Fq, Fr } from '@aztec/foundation/curves/bn254'; import type { Logger } from '@aztec/foundation/log'; import type { PXEConfig, PXECreationOptions } from '@aztec/pxe/client/lazy'; @@ -16,6 +17,7 @@ import { deriveSigningKey } from '@aztec/stdlib/keys'; import { ExecutionPayload, SimulationOverrides, + type TxExecutionRequest, type TxSimulationResult, collectOffchainEffects, mergeExecutionPayloads, @@ -52,10 +54,6 @@ export class EmbeddedWallet extends BaseWallet { } protected async getAccountFromAddress(address: AztecAddress): Promise { - if (address.equals(AztecAddress.ZERO)) { - return new SignerlessAccount(); - } - const { secretKey, salt, signingKey, type } = await this.walletDB.retrieveAccount(address); const accountManager = await this.createAccountInternal(type, secretKey, salt, signingKey); const account = await accountManager.getAccount(); @@ -116,7 +114,7 @@ export class EmbeddedWallet extends BaseWallet { offchainEffects.map(async effect => { try { const authRequest = await CallAuthorizationRequest.fromFields(effect.data); - return this.createAuthWit(opts.from, { + return this.createAuthWit(authRequest.onBehalfOf, { consumer: effect.contractAddress, innerHash: authRequest.innerHash, }); @@ -158,34 +156,36 @@ export class EmbeddedWallet extends BaseWallet { ): Promise { const { from, feeOptions, scopes, skipTxValidation, skipFeeEnforcement } = opts; + const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); + const finalExecutionPayload = feeExecutionPayload + ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) + : executionPayload; + const chainInfo = await this.getChainInfo(); + let overrides: SimulationOverrides | undefined; - let fromAccount: Account; - if (!from.equals(AztecAddress.ZERO)) { + let txRequest: TxExecutionRequest; + if (from === NO_FROM) { + const entrypoint = new DefaultEntrypoint(); + txRequest = await entrypoint.createTxExecutionRequest(finalExecutionPayload, feeOptions.gasSettings, chainInfo); + } else { const { account, instance, artifact } = await this.getFakeAccountDataFor(from); - fromAccount = account; overrides = { contracts: { [from.toString()]: { instance, artifact } }, }; - } else { - fromAccount = await this.getAccountFromAddress(from); + const executionOptions: DefaultAccountEntrypointOptions = { + txNonce: Fr.random(), + cancellable: this.cancellableTransactions, + // If from is an address, feeOptions include the way the account contract should handle the fee payment + feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions!, + }; + txRequest = await account.createTxExecutionRequest( + finalExecutionPayload, + feeOptions.gasSettings, + chainInfo, + executionOptions, + ); } - const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); - const executionOptions: DefaultAccountEntrypointOptions = { - txNonce: Fr.random(), - cancellable: this.cancellableTransactions, - feePaymentMethodOptions: feeOptions.accountFeePaymentMethodOptions, - }; - const finalExecutionPayload = feeExecutionPayload - ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) - : executionPayload; - const chainInfo = await this.getChainInfo(); - const txRequest = await fromAccount.createTxExecutionRequest( - finalExecutionPayload, - feeOptions.gasSettings, - chainInfo, - executionOptions, - ); return this.pxe.simulateTx(txRequest, { simulatePublic: true, skipFeeEnforcement, @@ -197,10 +197,7 @@ export class EmbeddedWallet extends BaseWallet { private async getFakeAccountDataFor(address: AztecAddress) { const originalAccount = await this.getAccountFromAddress(address); - if (originalAccount instanceof SignerlessAccount) { - throw new Error(`Cannot create fake account data for SignerlessAccount at address: ${address}`); - } - const originalAddress = (originalAccount as Account).getCompleteAddress(); + const originalAddress = originalAccount.getCompleteAddress(); const contractInstance = await this.pxe.getContractInstance(originalAddress.address); if (!contractInstance) { throw new Error(`No contract instance found for address: ${originalAddress.address}`); diff --git a/yarn-project/wallets/src/testing.ts b/yarn-project/wallets/src/testing.ts index 3b2723cde5b6..3f391caa9fe1 100644 --- a/yarn-project/wallets/src/testing.ts +++ b/yarn-project/wallets/src/testing.ts @@ -1,5 +1,6 @@ import type { InitialAccountData } from '@aztec/accounts/testing'; import { getInitialTestAccountsData } from '@aztec/accounts/testing/lazy'; +import { NO_FROM } from '@aztec/aztec.js/account'; import type { WaitOpts } from '@aztec/aztec.js/contracts'; import type { AccountManager } from '@aztec/aztec.js/wallet'; import type { Fq, Fr } from '@aztec/foundation/curves/bn254'; @@ -21,7 +22,7 @@ export async function deployFundedSchnorrAccounts( const accountManager = await wallet.createSchnorrAccount(secret, salt, signingKey); const deployMethod = await accountManager.getDeployMethod(); await deployMethod.send({ - from: AztecAddress.ZERO, + from: NO_FROM, skipClassPublication: i !== 0, wait: waitOptions, });