From 73a48bebc7eea8279712bbac377834748d6d642a Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 9 Jun 2026 13:20:33 +0200 Subject: [PATCH 01/23] initial structure --- noir-projects/noir-contracts/Nargo.toml | 2 + .../Nargo.toml | 10 ++ .../src/main.nr | 93 +++++++++++++++++++ .../Nargo.toml | 8 ++ .../src/main.nr | 55 +++++++++++ yarn-project/accounts/package.json | 10 +- .../accounts/scripts/copy-contracts.sh | 9 +- .../stub_account_contract.ts} | 0 .../src/{stub/ecdsa => ecdsa/stub}/index.ts | 2 +- .../src/{stub/ecdsa => ecdsa/stub}/lazy.ts | 2 +- .../accounts/src/schnorr/account_contract.ts | 1 + yarn-project/accounts/src/schnorr/index.ts | 52 +---------- .../src/schnorr/initializerless/index.ts | 52 +++++++++++ .../src/schnorr/initializerless/lazy.ts | 60 ++++++++++++ yarn-project/accounts/src/schnorr/lazy.ts | 62 +------------ .../src/schnorr/private_immutable/index.ts | 50 ++++++++++ .../src/schnorr/private_immutable/lazy.ts | 60 ++++++++++++ .../{stub/schnorr => schnorr/stub}/index.ts | 2 +- .../{stub/schnorr => schnorr/stub}/lazy.ts | 2 +- yarn-project/accounts/src/testing/index.ts | 2 +- yarn-project/accounts/src/testing/lazy.ts | 2 +- yarn-project/cli-wallet/src/utils/wallet.ts | 4 +- .../end-to-end/src/test-wallet/test_wallet.ts | 4 +- .../account-contract-providers/bundle.ts | 4 +- .../account-contract-providers/lazy.ts | 8 +- 25 files changed, 423 insertions(+), 133 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr create mode 100644 noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr rename yarn-project/accounts/src/{stub/account_contract.ts => defaults/stub_account_contract.ts} (100%) rename yarn-project/accounts/src/{stub/ecdsa => ecdsa/stub}/index.ts (94%) rename yarn-project/accounts/src/{stub/ecdsa => ecdsa/stub}/lazy.ts (94%) create mode 100644 yarn-project/accounts/src/schnorr/initializerless/index.ts create mode 100644 yarn-project/accounts/src/schnorr/initializerless/lazy.ts create mode 100644 yarn-project/accounts/src/schnorr/private_immutable/index.ts create mode 100644 yarn-project/accounts/src/schnorr/private_immutable/lazy.ts rename yarn-project/accounts/src/{stub/schnorr => schnorr/stub}/index.ts (94%) rename yarn-project/accounts/src/{stub/schnorr => schnorr/stub}/lazy.ts (94%) diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index b00add4b8724..67958f505613 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -3,8 +3,10 @@ members = [ "contracts/account/ecdsa_k_account_contract", "contracts/account/ecdsa_r_account_contract", "contracts/account/schnorr_account_contract", + "contracts/account/schnorr_initializerless_account_contract", "contracts/account/schnorr_hardcoded_account_contract", "contracts/account/simulated_schnorr_account_contract", + "contracts/account/simulated_schnorr_initializerless_account_contract", "contracts/account/simulated_ecdsa_account_contract", "contracts/app/amm_contract", "contracts/app/app_subscription_contract", diff --git a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/Nargo.toml new file mode 100644 index 000000000000..cc336f130028 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/Nargo.toml @@ -0,0 +1,10 @@ +[package] +name = "schnorr_initializerless_account_contract" +type = "contract" +compiler_version = ">=1.0.0" +authors = [""] + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } +schnorr = { tag = "v0.4.0", git = "https://github.com/noir-lang/schnorr" } + diff --git a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr new file mode 100644 index 000000000000..a02b1917a635 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr @@ -0,0 +1,93 @@ +use aztec::{ + macros::{aztec, AztecConfig}, + messages::{ + discovery::{ComputeNoteHash, ComputeNoteNullifier, CustomMessageHandler}, + processing::offchain::OffchainInboxSync, + }, + protocol::address::AztecAddress, +}; + +/// Empty override to opt-out of state syncing. This contract does not hold private state, +/// so runnning the sync process just results in unnecessary RPC calls +unconstrained fn no_sync( + _contract_address: AztecAddress, + _compute_note_hash: ComputeNoteHash, + _compute_note_nullifier: ComputeNoteNullifier, + _process_custom_message: Option, + _offchain_inbox_sync: Option, + _scope: AztecAddress, +) {} + +#[aztec(AztecConfig::new().custom_sync_state(crate::no_sync))] +pub contract SchnorrInitializerlessAccount { + use aztec::{ + authwit::{account::AccountActions, entrypoint::app::AppPayload}, + context::PrivateContext, + macros::functions::{allow_phase_change, external, view}, + oracle::{auth_witness::get_auth_witness, capsules::{load, store}, get_contract_instance::get_contract_instance}, + protocol::{hash::{poseidon2_hash, poseidon2_hash_bytes}, traits::{Deserialize, Serialize}}, + }; + + #[derive(Eq, Serialize, Deserialize)] + pub struct PublicKey { + pub x: Field, + pub y: Field, + } + + global PUB_KEY_SLOT: Field = comptime { poseidon2_hash_bytes("INITIALIZERLESS_ACCOUNT_PUB_KEY".as_bytes()) }; + + #[external("utility")] + unconstrained fn constructor(public_key: PublicKey) { + let serialized_pub_key = public_key.serialize(); + let expected = poseidon2_hash(public_key.serialize()); + let instance = aztec::oracle::get_contract_instance::get_contract_instance(self.address); + assert_eq( + expected, + instance.immutables_hash, + "Public key hash does not match the immutables hash, refusing to store", + ); + + store(self.address, PUB_KEY_SLOT, serialized_pub_key, self.address); + } + + #[external("private")] + #[allow_phase_change] + fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { + let actions = AccountActions::init(self.context, is_valid_impl); + actions.entrypoint(app_payload, fee_payment_method, cancellable); + } + + #[external("private")] + #[view] + fn verify_private_authwit(inner_hash: Field) -> Field { + let actions = AccountActions::init(self.context, is_valid_impl); + actions.verify_private_authwit(inner_hash) + } + + #[contract_library_method] + fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { + // Safety: The public key inside the capsule is checked to match the immutables_hash of this instance + let public_key = unsafe { load(context.this_address(), PUB_KEY_SLOT, context.this_address()) } + .map(|data| PublicKey::deserialize(data)) + .unwrap_or_else(|| panic( + "Public key was not stored in this PXE. Please call `constructor` first", + )); + + let expected = poseidon2_hash(public_key.serialize()); + let instance = get_contract_instance(context.this_address()); + + assert_eq(expected, instance.immutables_hash, "Immutables do not match instance immutables_hash"); + + // Safety: The witness is only used as a "magical value" that makes the + // signature verification below pass. + let limbs: [Field; 4] = unsafe { get_auth_witness(outer_hash) }; + let signature = ( + std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[0], limbs[1]), + std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[2], limbs[3]), + ); + + let pub_key = std::embedded_curve_ops::EmbeddedCurvePoint { x: public_key.x, y: public_key.y }; + + schnorr::verify_signature(pub_key, signature, outer_hash) + } +} diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml new file mode 100644 index 000000000000..d1937c1380f6 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "simulated_schnorr_initializerless_account_contract" +type = "contract" +compiler_version = ">=1.0.0" +authors = [""] + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr new file mode 100644 index 000000000000..6c7e17cf816d --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr @@ -0,0 +1,55 @@ +use aztec::{ + macros::{aztec, AztecConfig}, + messages::{ + discovery::{ComputeNoteHash, ComputeNoteNullifier, CustomMessageHandler}, + processing::offchain::OffchainInboxSync, + }, + protocol::address::AztecAddress, +}; + +/// Empty override to opt-out of state syncing. This contract does not hold private state, +/// so runnning the sync process just results in unnecessary RPC calls +unconstrained fn no_sync( + _contract_address: AztecAddress, + _compute_note_hash: ComputeNoteHash, + _compute_note_nullifier: ComputeNoteNullifier, + _process_custom_message: Option, + _offchain_inbox_sync: Option, + _scope: AztecAddress, +) {} + +// Stub account contract for SchnorrInitializerlessAccount, used during +// kernelless simulation. Matches the entrypoint + verify_private_authwit +// surface of the real account so PXE can resolve selectors against this +// class when the wallet overrides `currentContractClassId` for the +// instance. is_valid_impl returns true unconditionally -- auth witness +// generation is captured separately via the StubAuthWitnessProvider. +// +// No constructor: the real SchnorrInitializerlessAccount has no +// initializer (instances are constructed with initializationHash = 0). +#[aztec(AztecConfig::new().custom_sync_state(crate::no_sync))] +pub contract SimulatedSchnorrInitializerlessAccount { + use aztec::{ + authwit::{account::AccountActions, auth::IS_VALID_SELECTOR, entrypoint::app::AppPayload}, + context::PrivateContext, + macros::functions::{allow_phase_change, external, view}, + }; + + #[external("private")] + #[allow_phase_change] + fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { + let actions = AccountActions::init(self.context, is_valid_impl); + actions.entrypoint(app_payload, fee_payment_method, cancellable); + } + + #[external("private")] + #[view] + fn verify_private_authwit(_inner_hash: Field) -> Field { + IS_VALID_SELECTOR + } + + #[contract_library_method] + fn is_valid_impl(_context: &mut PrivateContext, _outer_hash: Field) -> bool { + true + } +} diff --git a/yarn-project/accounts/package.json b/yarn-project/accounts/package.json index 1cfdc55fb2c8..82208688f34b 100644 --- a/yarn-project/accounts/package.json +++ b/yarn-project/accounts/package.json @@ -8,16 +8,14 @@ "./defaults": "./dest/defaults/index.js", "./ecdsa": "./dest/ecdsa/index.js", "./ecdsa/lazy": "./dest/ecdsa/lazy.js", + "./ecdsa/stub": "./dest/ecdsa/stub/index.js", + "./ecdsa/stub/lazy": "./dest/ecdsa/stub/lazy.js", "./schnorr": "./dest/schnorr/index.js", "./schnorr/lazy": "./dest/schnorr/lazy.js", - "./stub/schnorr": "./dest/stub/schnorr/index.js", - "./stub/schnorr/lazy": "./dest/stub/schnorr/lazy.js", - "./stub/ecdsa": "./dest/stub/ecdsa/index.js", - "./stub/ecdsa/lazy": "./dest/stub/ecdsa/lazy.js", + "./schnorr/stub": "./dest/schnorr/stub/index.js", + "./schnorr/stub/lazy": "./dest/schnorr/stub/lazy.js", "./testing": "./dest/testing/index.js", "./testing/lazy": "./dest/testing/lazy.js", - "./copy-cat": "./dest/copy_cat/index.js", - "./copy-cat/lazy": "./dest/copy_cat/lazy.js", "./utils": "./dest/utils/index.js" }, "typedocOptions": { diff --git a/yarn-project/accounts/scripts/copy-contracts.sh b/yarn-project/accounts/scripts/copy-contracts.sh index c8babc835659..e5780d42c96f 100755 --- a/yarn-project/accounts/scripts/copy-contracts.sh +++ b/yarn-project/accounts/scripts/copy-contracts.sh @@ -2,7 +2,14 @@ set -euo pipefail mkdir -p ./artifacts -contracts=(schnorr_account_contract-SchnorrAccount ecdsa_k_account_contract-EcdsaKAccount ecdsa_r_account_contract-EcdsaRAccount simulated_schnorr_account_contract-SimulatedSchnorrAccount simulated_ecdsa_account_contract-SimulatedEcdsaAccount ) +contracts=( + schnorr_account_contract-SchnorrAccount \ + schnorr_initializerless_account_contract-SchnorrInitializerlessAccount \ + ecdsa_k_account_contract-EcdsaKAccount ecdsa_r_account_contract-EcdsaRAccount \ + simulated_schnorr_account_contract-SimulatedSchnorrAccount \ + simulated_schnorr_initializerless_account_contract-SimulatedSchnorrInitializerlessAccount \ + simulated_ecdsa_account_contract-SimulatedEcdsaAccount \ +) decl=$(cat < { - return Promise.resolve(SchnorrAccountContractArtifact); - } -} - -/** - * Compute the address of a schnorr account contract. - * @param secret - A seed for deriving the signing key and public keys. - * @param salt - The contract address salt. - * @param signingPrivateKey - A specific signing private key that's not derived from the secret. - */ -export async function getSchnorrAccountContractAddress( - secret: Fr, - salt: Fr, - signingPrivateKey?: GrumpkinScalar, -): Promise { - const signingKey = signingPrivateKey ?? deriveSigningKey(secret); - const accountContract = new SchnorrAccountContract(signingKey); - return await getAccountContractAddress(accountContract, secret, salt); -} +export * from './private_immutable/index.js'; +export * from './initializerless/index.js'; diff --git a/yarn-project/accounts/src/schnorr/initializerless/index.ts b/yarn-project/accounts/src/schnorr/initializerless/index.ts new file mode 100644 index 000000000000..06cc7695c1bb --- /dev/null +++ b/yarn-project/accounts/src/schnorr/initializerless/index.ts @@ -0,0 +1,52 @@ +/** + * The `@aztec/accounts/schnorr` export provides an account contract implementation that uses Schnorr signatures with a Grumpkin key for authentication, and a separate Grumpkin key for encryption. + * This is the suggested account contract type for most use cases within Aztec. + * + * @packageDocumentation + */ +import { getAccountContractAddress } from '@aztec/aztec.js/account'; +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; +import type { ContractArtifact } from '@aztec/stdlib/abi'; +import { loadContractArtifact } from '@aztec/stdlib/abi'; +import { deriveSigningKey } from '@aztec/stdlib/keys'; +import type { NoirCompiledContract } from '@aztec/stdlib/noir'; + +import SchnorrInitializerlessAccountContractJson from '../../../artifacts/SchnorrAccount.json' with { type: 'json' }; +import { SchnorrBaseAccountContract } from '../account_contract.js'; + +export const SchnorrInitializerlessAccountContractArtifact = loadContractArtifact( + SchnorrInitializerlessAccountContractJson as NoirCompiledContract, +); + +/** + * Account contract that authenticates transactions using Schnorr signatures + * verified against a Grumpkin public key stored in an immutable encrypted note. + * Eagerly loads the contract artifact + */ +export class SchnorrInitializerlessAccountContract extends SchnorrBaseAccountContract { + constructor(signingPrivateKey: GrumpkinScalar) { + super(signingPrivateKey); + } + + override getContractArtifact(): Promise { + return Promise.resolve(SchnorrInitializerlessAccountContractArtifact); + } +} + +/** + * Compute the address of a schnorr account contract. + * @param secret - A seed for deriving the signing key and public keys. + * @param salt - The contract address salt. + * @param signingPrivateKey - A specific signing private key that's not derived from the secret. + */ +export async function getSchnorrInitializerlessAccountContractAddress( + secret: Fr, + salt: Fr, + signingPrivateKey?: GrumpkinScalar, +): Promise { + const signingKey = signingPrivateKey ?? deriveSigningKey(secret); + const accountContract = new SchnorrInitializerlessAccountContract(signingKey); + return await getAccountContractAddress(accountContract, secret, salt); +} diff --git a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts new file mode 100644 index 000000000000..74d9ca5e4dae --- /dev/null +++ b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts @@ -0,0 +1,60 @@ +/** + * The `@aztec/accounts/schnorr` export provides an account contract implementation that uses Schnorr signatures with a Grumpkin key for authentication, and a separate Grumpkin key for encryption. + * This is the suggested account contract type for most use cases within Aztec. + * + * @packageDocumentation + */ +import { getAccountContractAddress } from '@aztec/aztec.js/account'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; +import type { ContractArtifact } from '@aztec/stdlib/abi'; +import { loadContractArtifact } from '@aztec/stdlib/abi'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { deriveSigningKey } from '@aztec/stdlib/keys'; + +import { SchnorrBaseAccountContract } from '../account_contract.js'; + +/** + * Lazily loads the contract artifact + * @returns The contract artifact for the schnorr account contract + */ +export async function getSchnorrInitializerlessAccountContractArtifact() { + // Cannot assert this import as it's incompatible with bundlers like vite + // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 + // Even if now supported by al major browsers, the MIME type is replaced with + // "text/javascript" + // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS + const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrAccount.json'); + return loadContractArtifact(schnorrAccountContractJson); +} + +/** + * Account contract that authenticates transactions using Schnorr signatures + * verified against a Grumpkin public key stored in an immutable encrypted note. + * Lazily loads the contract artifact + */ +export class SchnorrInitializerlessAccountContract extends SchnorrBaseAccountContract { + constructor(signingPrivateKey: GrumpkinScalar) { + super(signingPrivateKey); + } + + override getContractArtifact(): Promise { + return getSchnorrInitializerlessAccountContractArtifact(); + } +} + +/** + * Compute the address of a schnorr account contract. + * @param secret - A seed for deriving the signing key and public keys. + * @param salt - The contract address salt. + * @param signingPrivateKey - A specific signing private key that's not derived from the secret. + */ +export async function getSchnorrInitializerlessAccountContractAddress( + secret: Fr, + salt: Fr, + signingPrivateKey?: GrumpkinScalar, +): Promise { + const signingKey = signingPrivateKey ?? deriveSigningKey(secret); + const accountContract = new SchnorrInitializerlessAccountContract(signingKey); + return await getAccountContractAddress(accountContract, secret, salt); +} diff --git a/yarn-project/accounts/src/schnorr/lazy.ts b/yarn-project/accounts/src/schnorr/lazy.ts index 2413fe855cf2..d482534a96c2 100644 --- a/yarn-project/accounts/src/schnorr/lazy.ts +++ b/yarn-project/accounts/src/schnorr/lazy.ts @@ -1,60 +1,2 @@ -/** - * The `@aztec/accounts/schnorr` export provides an account contract implementation that uses Schnorr signatures with a Grumpkin key for authentication, and a separate Grumpkin key for encryption. - * This is the suggested account contract type for most use cases within Aztec. - * - * @packageDocumentation - */ -import { getAccountContractAddress } from '@aztec/aztec.js/account'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; -import type { ContractArtifact } from '@aztec/stdlib/abi'; -import { loadContractArtifact } from '@aztec/stdlib/abi'; -import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { deriveSigningKey } from '@aztec/stdlib/keys'; - -import { SchnorrBaseAccountContract } from './account_contract.js'; - -/** - * Lazily loads the contract artifact - * @returns The contract artifact for the schnorr account contract - */ -export async function getSchnorrAccountContractArtifact() { - // Cannot assert this import as it's incompatible with bundlers like vite - // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with - // "text/javascript" - // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS - const { default: schnorrAccountContractJson } = await import('../../artifacts/SchnorrAccount.json'); - return loadContractArtifact(schnorrAccountContractJson); -} - -/** - * Account contract that authenticates transactions using Schnorr signatures - * verified against a Grumpkin public key stored in an immutable encrypted note. - * Lazily loads the contract artifact - */ -export class SchnorrAccountContract extends SchnorrBaseAccountContract { - constructor(signingPrivateKey: GrumpkinScalar) { - super(signingPrivateKey); - } - - override getContractArtifact(): Promise { - return getSchnorrAccountContractArtifact(); - } -} - -/** - * Compute the address of a schnorr account contract. - * @param secret - A seed for deriving the signing key and public keys. - * @param salt - The contract address salt. - * @param signingPrivateKey - A specific signing private key that's not derived from the secret. - */ -export async function getSchnorrAccountContractAddress( - secret: Fr, - salt: Fr, - signingPrivateKey?: GrumpkinScalar, -): Promise { - const signingKey = signingPrivateKey ?? deriveSigningKey(secret); - const accountContract = new SchnorrAccountContract(signingKey); - return await getAccountContractAddress(accountContract, secret, salt); -} +export * from './private_immutable/lazy.js'; +export * from './initializerless/lazy.js'; diff --git a/yarn-project/accounts/src/schnorr/private_immutable/index.ts b/yarn-project/accounts/src/schnorr/private_immutable/index.ts new file mode 100644 index 000000000000..74e97d62049d --- /dev/null +++ b/yarn-project/accounts/src/schnorr/private_immutable/index.ts @@ -0,0 +1,50 @@ +/** + * The `@aztec/accounts/schnorr` export provides an account contract implementation that uses Schnorr signatures with a Grumpkin key for authentication, and a separate Grumpkin key for encryption. + * This is the suggested account contract type for most use cases within Aztec. + * + * @packageDocumentation + */ +import { getAccountContractAddress } from '@aztec/aztec.js/account'; +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; +import type { ContractArtifact } from '@aztec/stdlib/abi'; +import { loadContractArtifact } from '@aztec/stdlib/abi'; +import { deriveSigningKey } from '@aztec/stdlib/keys'; +import type { NoirCompiledContract } from '@aztec/stdlib/noir'; + +import SchnorrAccountContractJson from '../../../artifacts/SchnorrInitializerlessAccount.json' with { type: 'json' }; +import { SchnorrBaseAccountContract } from '../account_contract.js'; + +export const SchnorrAccountContractArtifact = loadContractArtifact(SchnorrAccountContractJson as NoirCompiledContract); + +/** + * Account contract that authenticates transactions using Schnorr signatures + * verified against a Grumpkin public key stored in an immutable encrypted note. + * Eagerly loads the contract artifact + */ +export class SchnorrAccountContract extends SchnorrBaseAccountContract { + constructor(signingPrivateKey: GrumpkinScalar) { + super(signingPrivateKey); + } + + override getContractArtifact(): Promise { + return Promise.resolve(SchnorrAccountContractArtifact); + } +} + +/** + * Compute the address of a schnorr account contract. + * @param secret - A seed for deriving the signing key and public keys. + * @param salt - The contract address salt. + * @param signingPrivateKey - A specific signing private key that's not derived from the secret. + */ +export async function getSchnorrAccountContractAddress( + secret: Fr, + salt: Fr, + signingPrivateKey?: GrumpkinScalar, +): Promise { + const signingKey = signingPrivateKey ?? deriveSigningKey(secret); + const accountContract = new SchnorrAccountContract(signingKey); + return await getAccountContractAddress(accountContract, secret, salt); +} diff --git a/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts b/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts new file mode 100644 index 000000000000..783237b173d4 --- /dev/null +++ b/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts @@ -0,0 +1,60 @@ +/** + * The `@aztec/accounts/schnorr` export provides an account contract implementation that uses Schnorr signatures with a Grumpkin key for authentication, and a separate Grumpkin key for encryption. + * This is the suggested account contract type for most use cases within Aztec. + * + * @packageDocumentation + */ +import { getAccountContractAddress } from '@aztec/aztec.js/account'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; +import type { ContractArtifact } from '@aztec/stdlib/abi'; +import { loadContractArtifact } from '@aztec/stdlib/abi'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { deriveSigningKey } from '@aztec/stdlib/keys'; + +import { SchnorrBaseAccountContract } from '../account_contract.js'; + +/** + * Lazily loads the contract artifact + * @returns The contract artifact for the schnorr account contract + */ +export async function getSchnorrAccountContractArtifact() { + // Cannot assert this import as it's incompatible with bundlers like vite + // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 + // Even if now supported by al major browsers, the MIME type is replaced with + // "text/javascript" + // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS + const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrInitializerlessAccount.json'); + return loadContractArtifact(schnorrAccountContractJson); +} + +/** + * Account contract that authenticates transactions using Schnorr signatures + * verified against a Grumpkin public key stored in an immutable encrypted note. + * Lazily loads the contract artifact + */ +export class SchnorrAccountContract extends SchnorrBaseAccountContract { + constructor(signingPrivateKey: GrumpkinScalar) { + super(signingPrivateKey); + } + + override getContractArtifact(): Promise { + return getSchnorrAccountContractArtifact(); + } +} + +/** + * Compute the address of a schnorr account contract. + * @param secret - A seed for deriving the signing key and public keys. + * @param salt - The contract address salt. + * @param signingPrivateKey - A specific signing private key that's not derived from the secret. + */ +export async function getSchnorrAccountContractAddress( + secret: Fr, + salt: Fr, + signingPrivateKey?: GrumpkinScalar, +): Promise { + const signingKey = signingPrivateKey ?? deriveSigningKey(secret); + const accountContract = new SchnorrAccountContract(signingKey); + return await getAccountContractAddress(accountContract, secret, salt); +} diff --git a/yarn-project/accounts/src/stub/schnorr/index.ts b/yarn-project/accounts/src/schnorr/stub/index.ts similarity index 94% rename from yarn-project/accounts/src/stub/schnorr/index.ts rename to yarn-project/accounts/src/schnorr/stub/index.ts index 640343cd0c5b..2e166ef3714a 100644 --- a/yarn-project/accounts/src/stub/schnorr/index.ts +++ b/yarn-project/accounts/src/schnorr/stub/index.ts @@ -5,7 +5,7 @@ import { loadContractArtifact } from '@aztec/stdlib/abi'; import type { NoirCompiledContract } from '@aztec/stdlib/noir'; import SimulatedSchnorrAccountJson from '../../../artifacts/SimulatedSchnorrAccount.json' with { type: 'json' }; -import { StubBaseAccountContract } from '../account_contract.js'; +import { StubBaseAccountContract } from '../../defaults/stub_account_contract.js'; export const StubSchnorrAccountContractArtifact = loadContractArtifact( SimulatedSchnorrAccountJson as NoirCompiledContract, diff --git a/yarn-project/accounts/src/stub/schnorr/lazy.ts b/yarn-project/accounts/src/schnorr/stub/lazy.ts similarity index 94% rename from yarn-project/accounts/src/stub/schnorr/lazy.ts rename to yarn-project/accounts/src/schnorr/stub/lazy.ts index 413a42f3692b..f7382bf6fb34 100644 --- a/yarn-project/accounts/src/stub/schnorr/lazy.ts +++ b/yarn-project/accounts/src/schnorr/stub/lazy.ts @@ -3,7 +3,7 @@ import type { CompleteAddress } from '@aztec/aztec.js/addresses'; import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account'; import { loadContractArtifact } from '@aztec/stdlib/abi'; -import { StubBaseAccountContract } from '../account_contract.js'; +import { StubBaseAccountContract } from '../../defaults/stub_account_contract.js'; /** * Lazily loads the Schnorr stub contract artifact (browser-compatible). diff --git a/yarn-project/accounts/src/testing/index.ts b/yarn-project/accounts/src/testing/index.ts index f5c9df795d66..549e99311797 100644 --- a/yarn-project/accounts/src/testing/index.ts +++ b/yarn-project/accounts/src/testing/index.ts @@ -6,7 +6,7 @@ import { Fr } from '@aztec/aztec.js/fields'; import { deriveSigningKey } from '@aztec/stdlib/keys'; -import { getSchnorrAccountContractAddress } from '../schnorr/index.js'; +import { getSchnorrAccountContractAddress } from '../schnorr/private_immutable/index.js'; import { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_ENCRYPTION_KEYS, diff --git a/yarn-project/accounts/src/testing/lazy.ts b/yarn-project/accounts/src/testing/lazy.ts index 360674f3a8d9..94a27eb6fc1b 100644 --- a/yarn-project/accounts/src/testing/lazy.ts +++ b/yarn-project/accounts/src/testing/lazy.ts @@ -6,7 +6,7 @@ import { Fr } from '@aztec/aztec.js/fields'; import { deriveSigningKey } from '@aztec/stdlib/keys'; -import { getSchnorrAccountContractAddress } from '../schnorr/lazy.js'; +import { getSchnorrAccountContractAddress } from '../schnorr/private_immutable/lazy.js'; import { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_ENCRYPTION_KEYS, diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index 8e0a491ba70c..e4ff836941de 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -1,7 +1,7 @@ import { EcdsaRAccountContract, EcdsaRSSHAccountContract } from '@aztec/accounts/ecdsa'; +import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/ecdsa/stub'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/stub/ecdsa'; -import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr'; +import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/schnorr/stub'; import { getIdentities } from '@aztec/accounts/utils'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import { type InteractionFeeOptions, getContractClassFromArtifact, getGasLimits } from '@aztec/aztec.js/contracts'; 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 fba928c618d9..bb0662ad9640 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 { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/ecdsa/stub'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/stub/ecdsa'; -import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr'; +import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/schnorr/stub'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import type { CompleteAddress } from '@aztec/aztec.js/addresses'; import { diff --git a/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts b/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts index 30f07f9c87c2..c10ebfc25375 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts @@ -1,7 +1,7 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; +import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/ecdsa/stub'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/stub/ecdsa'; -import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr'; +import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/schnorr/stub'; import type { Account, AccountContract } from '@aztec/aztec.js/account'; import type { Fq } from '@aztec/foundation/curves/bn254'; import type { ContractArtifact } from '@aztec/stdlib/abi'; diff --git a/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts b/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts index 178683e354c2..0edcd9007c94 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts @@ -28,20 +28,20 @@ export class LazyAccountContractsProvider implements AccountContractsProvider { async getStubAccountContractArtifact(type: AccountType): Promise { if (type === 'schnorr') { - const { getStubSchnorrAccountContractArtifact } = await import('@aztec/accounts/stub/schnorr/lazy'); + const { getStubSchnorrAccountContractArtifact } = await import('@aztec/accounts/schnorr/stub/lazy'); return getStubSchnorrAccountContractArtifact(); } else { - const { getStubEcdsaAccountContractArtifact } = await import('@aztec/accounts/stub/ecdsa/lazy'); + const { getStubEcdsaAccountContractArtifact } = await import('@aztec/accounts/ecdsa/stub/lazy'); return getStubEcdsaAccountContractArtifact(); } } async createStubAccount(address: CompleteAddress, type: AccountType): Promise { if (type === 'schnorr') { - const { createStubSchnorrAccount } = await import('@aztec/accounts/stub/schnorr/lazy'); + const { createStubSchnorrAccount } = await import('@aztec/accounts/schnorr/stub/lazy'); return createStubSchnorrAccount(address); } else { - const { createStubEcdsaAccount } = await import('@aztec/accounts/stub/ecdsa/lazy'); + const { createStubEcdsaAccount } = await import('@aztec/accounts/ecdsa/stub/lazy'); return createStubEcdsaAccount(address); } } From a82241b78f753f242d974300599413ef427a8725 Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 9 Jun 2026 13:25:42 +0200 Subject: [PATCH 02/23] removed useless contract --- noir-projects/noir-contracts/Nargo.toml | 1 - .../Nargo.toml | 8 --- .../src/main.nr | 55 ------------------- .../crates/serde/src/serialization.nr | 8 +-- .../crates/serde/src/type_impls.nr | 10 ++-- .../crates/types/src/meta/mod.nr | 6 +- .../accounts/scripts/copy-contracts.sh | 4 +- 7 files changed, 14 insertions(+), 78 deletions(-) delete mode 100644 noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml delete mode 100644 noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 67958f505613..c4309c321917 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -6,7 +6,6 @@ members = [ "contracts/account/schnorr_initializerless_account_contract", "contracts/account/schnorr_hardcoded_account_contract", "contracts/account/simulated_schnorr_account_contract", - "contracts/account/simulated_schnorr_initializerless_account_contract", "contracts/account/simulated_ecdsa_account_contract", "contracts/app/amm_contract", "contracts/app/app_subscription_contract", diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml deleted file mode 100644 index d1937c1380f6..000000000000 --- a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/Nargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "simulated_schnorr_initializerless_account_contract" -type = "contract" -compiler_version = ">=1.0.0" -authors = [""] - -[dependencies] -aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr deleted file mode 100644 index 6c7e17cf816d..000000000000 --- a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_initializerless_account_contract/src/main.nr +++ /dev/null @@ -1,55 +0,0 @@ -use aztec::{ - macros::{aztec, AztecConfig}, - messages::{ - discovery::{ComputeNoteHash, ComputeNoteNullifier, CustomMessageHandler}, - processing::offchain::OffchainInboxSync, - }, - protocol::address::AztecAddress, -}; - -/// Empty override to opt-out of state syncing. This contract does not hold private state, -/// so runnning the sync process just results in unnecessary RPC calls -unconstrained fn no_sync( - _contract_address: AztecAddress, - _compute_note_hash: ComputeNoteHash, - _compute_note_nullifier: ComputeNoteNullifier, - _process_custom_message: Option, - _offchain_inbox_sync: Option, - _scope: AztecAddress, -) {} - -// Stub account contract for SchnorrInitializerlessAccount, used during -// kernelless simulation. Matches the entrypoint + verify_private_authwit -// surface of the real account so PXE can resolve selectors against this -// class when the wallet overrides `currentContractClassId` for the -// instance. is_valid_impl returns true unconditionally -- auth witness -// generation is captured separately via the StubAuthWitnessProvider. -// -// No constructor: the real SchnorrInitializerlessAccount has no -// initializer (instances are constructed with initializationHash = 0). -#[aztec(AztecConfig::new().custom_sync_state(crate::no_sync))] -pub contract SimulatedSchnorrInitializerlessAccount { - use aztec::{ - authwit::{account::AccountActions, auth::IS_VALID_SELECTOR, entrypoint::app::AppPayload}, - context::PrivateContext, - macros::functions::{allow_phase_change, external, view}, - }; - - #[external("private")] - #[allow_phase_change] - fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { - let actions = AccountActions::init(self.context, is_valid_impl); - actions.entrypoint(app_payload, fee_payment_method, cancellable); - } - - #[external("private")] - #[view] - fn verify_private_authwit(_inner_hash: Field) -> Field { - IS_VALID_SELECTOR - } - - #[contract_library_method] - fn is_valid_impl(_context: &mut PrivateContext, _outer_hash: Field) -> bool { - true - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/serde/src/serialization.nr b/noir-projects/noir-protocol-circuits/crates/serde/src/serialization.nr index 96ee60690b9d..4edd83db54c3 100644 --- a/noir-projects/noir-protocol-circuits/crates/serde/src/serialization.nr +++ b/noir-projects/noir-protocol-circuits/crates/serde/src/serialization.nr @@ -90,7 +90,7 @@ pub comptime fn derive_serialize(s: TypeDefinition) -> Quoted { // Generates the generic parameter declarations (to be placed after the `impl` keyword) and the `where` clause // for the `Serialize` trait. let generics_declarations = get_generics_declarations(s); - let where_serialize_clause = get_where_trait_clause(s, quote {Serialize}); + let where_serialize_clause = get_where_trait_clause(s, quote { Serialize }); let params_len_quote = get_params_len_quote(params); @@ -212,7 +212,7 @@ pub comptime fn derive_deserialize(s: TypeDefinition) -> Quoted { // Generates the generic parameter declarations (to be placed after the `impl` keyword) and the `where` clause // for the `Deserialize` trait. let generics_declarations = get_generics_declarations(s); - let where_deserialize_clause = get_where_trait_clause(s, quote {Deserialize}); + let where_deserialize_clause = get_where_trait_clause(s, quote { Deserialize }); // The following will give us: // ::N + ::N + ... @@ -226,7 +226,7 @@ pub comptime fn derive_deserialize(s: TypeDefinition) -> Quoted { }) .join(quote {+}) } else { - quote {0} + quote { 0 } }; // For structs containing a single member, we can enhance performance by directly deserializing the input array, @@ -325,7 +325,7 @@ comptime fn get_generics_declarations(s: TypeDefinition) -> Quoted { quote {let $name: $integer_type} } else { // The generic is not numeric, so we return a quote containing the name of the generic (e.g. "T") - quote {$name} + quote { $name } } }) .join(quote {,}); diff --git a/noir-projects/noir-protocol-circuits/crates/serde/src/type_impls.nr b/noir-projects/noir-protocol-circuits/crates/serde/src/type_impls.nr index 7400319af14f..3ded4402c7d1 100644 --- a/noir-projects/noir-protocol-circuits/crates/serde/src/type_impls.nr +++ b/noir-projects/noir-protocol-circuits/crates/serde/src/type_impls.nr @@ -637,15 +637,15 @@ comptime fn impl_serialize_for_tuple(_m: Module, length: u32) -> Quoted { // `::N + ::N + ::N` let full_size_serialize = type_names .map(|type_name| quote { - <$type_name as Serialize>::N - }) + <$type_name as Serialize>::N + }) .join(quote [+]); // `::N + ::N + ::N` let full_size_deserialize = type_names .map(|type_name| quote { - <$type_name as Deserialize>::N - }) + <$type_name as Deserialize>::N + }) .join(quote [+]); // `T0: Serialize, T1: Serialize, T2: Serialize,` @@ -666,7 +666,7 @@ comptime fn impl_serialize_for_tuple(_m: Module, length: u32) -> Quoted { let serialized_fields = type_names .mapi(|i, _type_name| quote { $crate::serialization::Serialize::stream_serialize(self.$i, writer); - }) + }) .join(quote []); // Statements to deserialize each field diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr index 4ca42ab7ed99..e68a2eeec499 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr @@ -42,7 +42,7 @@ comptime fn get_generics_declarations(s: TypeDefinition) -> Quoted { quote {let $name: $integer_type} } else { // The generic is not numeric, so we return a quote containing the name of the generic (e.g. "T") - quote {$name} + quote { $name } } }) .join(quote {,}); @@ -170,7 +170,7 @@ pub comptime fn derive_packable(s: TypeDefinition) -> Quoted { // Generates the generic parameter declarations (to be placed after the `impl` keyword) and the `where` clause // for the `Packable` trait. let generics_declarations = get_generics_declarations(s); - let where_packable_clause = get_where_trait_clause(s, quote {Packable}); + let where_packable_clause = get_where_trait_clause(s, quote { Packable }); // The following will give us: // ::N + ::N + ... @@ -184,7 +184,7 @@ pub comptime fn derive_packable(s: TypeDefinition) -> Quoted { }) .join(quote {+}) } else { - quote {0} + quote { 0 } }; // For structs containing a single member, we can enhance performance by directly returning the packed member, diff --git a/yarn-project/accounts/scripts/copy-contracts.sh b/yarn-project/accounts/scripts/copy-contracts.sh index e5780d42c96f..7e9a3266f308 100755 --- a/yarn-project/accounts/scripts/copy-contracts.sh +++ b/yarn-project/accounts/scripts/copy-contracts.sh @@ -5,9 +5,9 @@ mkdir -p ./artifacts contracts=( schnorr_account_contract-SchnorrAccount \ schnorr_initializerless_account_contract-SchnorrInitializerlessAccount \ - ecdsa_k_account_contract-EcdsaKAccount ecdsa_r_account_contract-EcdsaRAccount \ + ecdsa_k_account_contract-EcdsaKAccount \ + ecdsa_r_account_contract-EcdsaRAccount \ simulated_schnorr_account_contract-SimulatedSchnorrAccount \ - simulated_schnorr_initializerless_account_contract-SimulatedSchnorrInitializerlessAccount \ simulated_ecdsa_account_contract-SimulatedEcdsaAccount \ ) From 17480b13998dc33d2d608ee381b1ebcf015553d7 Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 9 Jun 2026 13:57:55 +0200 Subject: [PATCH 03/23] wip --- .../src/embedded/account-contract-providers/bundle.ts | 6 +++++- .../wallets/src/embedded/account-contract-providers/lazy.ts | 5 +++++ .../src/embedded/account-contract-providers/types.ts | 1 + yarn-project/wallets/src/embedded/embedded_wallet.ts | 5 +++++ yarn-project/wallets/src/embedded/wallet_db.ts | 2 +- 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts b/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts index c10ebfc25375..cff7f0f89afe 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts @@ -1,6 +1,6 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/ecdsa/stub'; -import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; +import { SchnorrAccountContract, SchnorrInitializerlessAccountContract } from '@aztec/accounts/schnorr'; import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/schnorr/stub'; import type { Account, AccountContract } from '@aztec/aztec.js/account'; import type { Fq } from '@aztec/foundation/curves/bn254'; @@ -19,6 +19,10 @@ export class BundleAccountContractsProvider implements AccountContractsProvider return Promise.resolve(new SchnorrAccountContract(signingKey)); } + getSchnorrInitializerlessAccountContract(signingKey: Fq): Promise { + return Promise.resolve(new SchnorrInitializerlessAccountContract(signingKey)); + } + getEcdsaRAccountContract(signingKey: Buffer): Promise { return Promise.resolve(new EcdsaRAccountContract(signingKey)); } diff --git a/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts b/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts index 0edcd9007c94..bec3d9c5d20f 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts @@ -16,6 +16,11 @@ export class LazyAccountContractsProvider implements AccountContractsProvider { return new SchnorrAccountContract(signingKey); } + async getSchnorrInitializerlessAccountContract(signingKey: Fq): Promise { + const { SchnorrInitializerlessAccountContract } = await import('@aztec/accounts/schnorr/lazy'); + return new SchnorrInitializerlessAccountContract(signingKey); + } + async getEcdsaRAccountContract(signingKey: Buffer): Promise { const { EcdsaRAccountContract } = await import('@aztec/accounts/ecdsa/lazy'); return new EcdsaRAccountContract(signingKey); diff --git a/yarn-project/wallets/src/embedded/account-contract-providers/types.ts b/yarn-project/wallets/src/embedded/account-contract-providers/types.ts index b1b546e160e4..0c189c7b3634 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/types.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/types.ts @@ -13,6 +13,7 @@ import type { AccountType } from '../wallet_db.js'; */ export interface AccountContractsProvider { getSchnorrAccountContract(signingKey: Fq): Promise; + getSchnorrInitializerlessAccountContract(signingKey: Fq): Promise; getEcdsaRAccountContract(signingKey: Buffer): Promise; getEcdsaKAccountContract(signingKey: Buffer): Promise; getStubAccountContractArtifact(type: AccountType): Promise; diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index 1e30d72533ea..65e15e61dd90 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -284,6 +284,7 @@ export class EmbeddedWallet extends BaseWallet { await this.pxe.registerContractClass(ecdsaArtifact); this.stubClassIds.set('schnorr', schnorrClassId); + this.stubClassIds.set('schnorr_initializerless', schnorrClassId); this.stubClassIds.set('ecdsasecp256k1', ecdsaClassId); this.stubClassIds.set('ecdsasecp256r1', ecdsaClassId); } @@ -393,6 +394,10 @@ export class EmbeddedWallet extends BaseWallet { contract = await this.accountContracts.getSchnorrAccountContract(Fq.fromBuffer(signingKey)); break; } + case 'schnorr_initializerless': { + contract = await this.accountContracts.getSchnorrInitializerlessAccountContract(Fq.fromBuffer(signingKey)); + break; + } case 'ecdsasecp256k1': { contract = await this.accountContracts.getEcdsaKAccountContract(signingKey); break; diff --git a/yarn-project/wallets/src/embedded/wallet_db.ts b/yarn-project/wallets/src/embedded/wallet_db.ts index b32a84ac4d43..e6d8e1a47491 100644 --- a/yarn-project/wallets/src/embedded/wallet_db.ts +++ b/yarn-project/wallets/src/embedded/wallet_db.ts @@ -4,7 +4,7 @@ import type { LogFn } from '@aztec/foundation/log'; import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -export const AccountTypes = ['schnorr', 'ecdsasecp256r1', 'ecdsasecp256k1'] as const; +export const AccountTypes = ['schnorr', 'schnorr_initializerless', 'ecdsasecp256r1', 'ecdsasecp256k1'] as const; export type AccountType = (typeof AccountTypes)[number]; function accountKey(field: string, address: AztecAddress | string): string { From 28eec02947817dc510d225cb2ea072027e1a52e0 Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 9 Jun 2026 13:59:43 +0200 Subject: [PATCH 04/23] lint --- yarn-project/accounts/src/schnorr/account_contract.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn-project/accounts/src/schnorr/account_contract.ts b/yarn-project/accounts/src/schnorr/account_contract.ts index d20d1d802229..45fbf9658489 100644 --- a/yarn-project/accounts/src/schnorr/account_contract.ts +++ b/yarn-project/accounts/src/schnorr/account_contract.ts @@ -4,7 +4,6 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import { AuthWitness } from '@aztec/stdlib/auth-witness'; import { CompleteAddress } from '@aztec/stdlib/contract'; -import { deriveSigningKey } from '@aztec/stdlib/keys'; import { DefaultAccountContract } from '../defaults/account_contract.js'; From c61c9e08c1208aae80d03f7be1adbb4da7303048 Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 9 Jun 2026 14:28:08 +0200 Subject: [PATCH 05/23] fixes --- yarn-project/accounts/src/schnorr/initializerless/index.ts | 2 +- yarn-project/accounts/src/schnorr/initializerless/lazy.ts | 2 +- yarn-project/accounts/src/schnorr/private_immutable/index.ts | 2 +- yarn-project/accounts/src/schnorr/private_immutable/lazy.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn-project/accounts/src/schnorr/initializerless/index.ts b/yarn-project/accounts/src/schnorr/initializerless/index.ts index 06cc7695c1bb..e9d467160a8c 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/index.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/index.ts @@ -13,7 +13,7 @@ import { loadContractArtifact } from '@aztec/stdlib/abi'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import type { NoirCompiledContract } from '@aztec/stdlib/noir'; -import SchnorrInitializerlessAccountContractJson from '../../../artifacts/SchnorrAccount.json' with { type: 'json' }; +import SchnorrInitializerlessAccountContractJson from '../../../artifacts/SchnorrInitializerlessAccount.json' with { type: 'json' }; import { SchnorrBaseAccountContract } from '../account_contract.js'; export const SchnorrInitializerlessAccountContractArtifact = loadContractArtifact( diff --git a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts index 74d9ca5e4dae..cd7b48edbf3a 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts @@ -24,7 +24,7 @@ export async function getSchnorrInitializerlessAccountContractArtifact() { // Even if now supported by al major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS - const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrAccount.json'); + const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrInitializerlessAccount.json'); return loadContractArtifact(schnorrAccountContractJson); } diff --git a/yarn-project/accounts/src/schnorr/private_immutable/index.ts b/yarn-project/accounts/src/schnorr/private_immutable/index.ts index 74e97d62049d..63d57997d5a9 100644 --- a/yarn-project/accounts/src/schnorr/private_immutable/index.ts +++ b/yarn-project/accounts/src/schnorr/private_immutable/index.ts @@ -13,7 +13,7 @@ import { loadContractArtifact } from '@aztec/stdlib/abi'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import type { NoirCompiledContract } from '@aztec/stdlib/noir'; -import SchnorrAccountContractJson from '../../../artifacts/SchnorrInitializerlessAccount.json' with { type: 'json' }; +import SchnorrAccountContractJson from '../../../artifacts/SchnorrAccount.json' with { type: 'json' }; import { SchnorrBaseAccountContract } from '../account_contract.js'; export const SchnorrAccountContractArtifact = loadContractArtifact(SchnorrAccountContractJson as NoirCompiledContract); diff --git a/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts b/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts index 783237b173d4..a50f5b083db2 100644 --- a/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts +++ b/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts @@ -24,7 +24,7 @@ export async function getSchnorrAccountContractArtifact() { // Even if now supported by al major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS - const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrInitializerlessAccount.json'); + const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrAccount.json'); return loadContractArtifact(schnorrAccountContractJson); } From f71d4f510413eb4fb2ec6aee2dab2ceb9f22602a Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 9 Jun 2026 21:47:00 +0200 Subject: [PATCH 06/23] working bot --- .../src/main.nr | 25 +- yarn-project/bot/src/factory.ts | 262 ++++-------------- yarn-project/end-to-end/src/e2e_bot.test.ts | 16 +- .../wallets/src/embedded/embedded_wallet.ts | 26 +- 4 files changed, 99 insertions(+), 230 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr index a02b1917a635..dd6321fcda62 100644 --- a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr @@ -27,19 +27,13 @@ pub contract SchnorrInitializerlessAccount { oracle::{auth_witness::get_auth_witness, capsules::{load, store}, get_contract_instance::get_contract_instance}, protocol::{hash::{poseidon2_hash, poseidon2_hash_bytes}, traits::{Deserialize, Serialize}}, }; - - #[derive(Eq, Serialize, Deserialize)] - pub struct PublicKey { - pub x: Field, - pub y: Field, - } + use std::embedded_curve_ops::EmbeddedCurvePoint; global PUB_KEY_SLOT: Field = comptime { poseidon2_hash_bytes("INITIALIZERLESS_ACCOUNT_PUB_KEY".as_bytes()) }; #[external("utility")] - unconstrained fn constructor(public_key: PublicKey) { - let serialized_pub_key = public_key.serialize(); - let expected = poseidon2_hash(public_key.serialize()); + unconstrained fn constructor(signing_pub_key_x: Field, signing_pub_key_y: Field) { + let expected = poseidon2_hash([signing_pub_key_x, signing_pub_key_y]); let instance = aztec::oracle::get_contract_instance::get_contract_instance(self.address); assert_eq( expected, @@ -47,7 +41,12 @@ pub contract SchnorrInitializerlessAccount { "Public key hash does not match the immutables hash, refusing to store", ); - store(self.address, PUB_KEY_SLOT, serialized_pub_key, self.address); + store( + self.address, + PUB_KEY_SLOT, + [signing_pub_key_x, signing_pub_key_y], + self.address, + ); } #[external("private")] @@ -68,7 +67,7 @@ pub contract SchnorrInitializerlessAccount { fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { // Safety: The public key inside the capsule is checked to match the immutables_hash of this instance let public_key = unsafe { load(context.this_address(), PUB_KEY_SLOT, context.this_address()) } - .map(|data| PublicKey::deserialize(data)) + .map(|data| EmbeddedCurvePoint::deserialize(data)) .unwrap_or_else(|| panic( "Public key was not stored in this PXE. Please call `constructor` first", )); @@ -86,8 +85,6 @@ pub contract SchnorrInitializerlessAccount { std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[2], limbs[3]), ); - let pub_key = std::embedded_curve_ops::EmbeddedCurvePoint { x: public_key.x, y: public_key.y }; - - schnorr::verify_signature(pub_key, signature, outer_hash) + schnorr::verify_signature(public_key, signature, outer_hash) } } diff --git a/yarn-project/bot/src/factory.ts b/yarn-project/bot/src/factory.ts index aa04f1c75dbd..fc8a6e26140a 100644 --- a/yarn-project/bot/src/factory.ts +++ b/yarn-project/bot/src/factory.ts @@ -1,5 +1,4 @@ import { getInitialTestAccountsData } from '@aztec/accounts/testing'; -import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { BatchCall, @@ -17,20 +16,17 @@ import { createLogger } from '@aztec/aztec.js/log'; import { waitForL1ToL2MessageReady } from '@aztec/aztec.js/messaging'; import { waitForTx } from '@aztec/aztec.js/node'; import { getFeeJuiceBalance } from '@aztec/aztec.js/utils'; -import { ContractInitializationStatus } from '@aztec/aztec.js/wallet'; import { createEthereumChain } from '@aztec/ethereum/chain'; import { createExtendedL1Client } from '@aztec/ethereum/client'; import { RollupContract } from '@aztec/ethereum/contracts'; import type { ExtendedViemWalletClient } from '@aztec/ethereum/types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; -import { Timer } from '@aztec/foundation/timer'; import { AMMContract } from '@aztec/noir-contracts.js/AMM'; import { PrivateTokenContract } from '@aztec/noir-contracts.js/PrivateToken'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { TestContract } from '@aztec/noir-test-contracts.js/Test'; import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract'; -import { GasFees, GasSettings, ManaUsageEstimate } from '@aztec/stdlib/gas'; import type { AztecNode, AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; @@ -43,7 +39,6 @@ import { getBalances, getPrivateBalance, isStandardTokenContract } from './utils const MINT_BALANCE = 1e12; const MIN_BALANCE = 1e3; const FEE_JUICE_TOP_UP_THRESHOLD = 100n * 10n ** 18n; -const FEE_JUICE_TOP_UP_TARGET = 10_000n * 10n ** 18n; export class BotFactory { private log = createLogger('bot'); @@ -73,8 +68,8 @@ export class BotFactory { }> { const defaultAccountAddress = await this.setupAccount(); const recipient = (await this.wallet.createSchnorrAccount(Fr.random(), Fr.random())).address; - const token = await this.setupTokenWithOptionalEarlyRefuel(defaultAccountAddress); - await this.ensureFeeJuiceBalance(defaultAccountAddress, token); + await this.ensureFeeJuiceBalance(defaultAccountAddress); + const token = await this.setupToken(defaultAccountAddress); await this.mintTokens(token, defaultAccountAddress); return { wallet: this.wallet, defaultAccountAddress, token, node: this.aztecNode, recipient }; } @@ -88,13 +83,8 @@ export class BotFactory { node: AztecNode; }> { const defaultAccountAddress = await this.setupAccount(); - const token0 = await this.setupTokenContractWithOptionalEarlyRefuel( - defaultAccountAddress, - this.config.tokenSalt, - 'BotToken0', - 'BOT0', - ); - await this.ensureFeeJuiceBalance(defaultAccountAddress, token0); + await this.ensureFeeJuiceBalance(defaultAccountAddress); + const token0 = await this.setupTokenContract(defaultAccountAddress, this.config.tokenSalt, 'BotToken0', 'BOT0'); const token1 = await this.setupTokenContract(defaultAccountAddress, this.config.tokenSalt, 'BotToken1', 'BOT1'); const liquidityToken = await this.setupTokenContract( defaultAccountAddress, @@ -129,6 +119,7 @@ export class BotFactory { rollupVersion: bigint; }> { const defaultAccountAddress = await this.setupAccount(); + await this.ensureFeeJuiceBalance(defaultAccountAddress); // Create L1 client (same pattern as bridgeL1FeeJuice) const l1RpcUrls = this.config.l1RpcUrls; @@ -147,7 +138,7 @@ export class BotFactory { const rollupContract = new RollupContract(l1Client, l1ContractAddresses.rollupAddress.toString()); const rollupVersion = await rollupContract.getVersion(); - // Deploy TestContract + // Deploy TestContract (pays from the standing balance funded above). const contract = await this.setupTestContract(defaultAccountAddress); // Recover any pending messages from store (clean up stale ones first) @@ -210,45 +201,12 @@ export class BotFactory { } } - private async setupAccountWithPrivateKey(secret: Fr) { - const salt = this.config.senderSalt ?? Fr.ONE; - const signingKey = deriveSigningKey(secret); - const accountManager = await this.wallet.createSchnorrAccount(secret, salt, signingKey); - const metadata = await this.wallet.getContractMetadata(accountManager.address); - if (metadata.initializationStatus === ContractInitializationStatus.INITIALIZED) { - this.log.info(`Account at ${accountManager.address.toString()} already initialized`); - const timer = new Timer(); - const address = accountManager.address; - this.log.info(`Account at ${address} registered. duration=${timer.ms()}`); - await this.store.deleteBridgeClaim(address); - return address; - } else { - const address = accountManager.address; - this.log.info(`Deploying account at ${address}`); - - const claim = await this.getOrCreateBridgeClaim(address); - - const paymentMethod = new FeeJuicePaymentMethodWithClaim(accountManager.address, claim); - const deployMethod = await accountManager.getDeployMethod(); - - await this.withNoMinTxsPerBlock(async () => { - const { txHash } = await deployMethod.send({ - from: NO_FROM, - fee: { paymentMethod }, - wait: NO_WAIT, - }); - this.log.info(`Sent tx for account deployment with hash ${txHash.toString()}`); - return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); - }); - this.log.info(`Account deployed at ${address}`); - - // Clean up the consumed bridge claim - await this.store.deleteBridgeClaim(address); - - return accountManager.address; - } - } - + /** + * Keyless fallback for tests and local dev: reuses the first genesis test account, whose schnorr address + * is pre-funded with fee juice via `initialFundedAccounts`. It must be a (non-initializerless) schnorr + * account so the address matches the funded one. Production bots set a sender private key and fund the + * resulting initializerless account from L1 instead; see setupAccountWithPrivateKey. + */ private async setupTestAccount() { const [initialAccountData] = await getInitialTestAccountsData(); const accountManager = await this.wallet.createSchnorrAccount( @@ -259,62 +217,11 @@ export class BotFactory { return accountManager.address; } - /** - * Setup token and refuel first: if the token already exists (restart scenario), - * run ensureFeeJuiceBalance before any step that might need fee juice. When deploying, - * use a bridge claim if balance is below threshold. - */ - private async setupTokenWithOptionalEarlyRefuel(sender: AztecAddress): Promise { - const token = await this.getTokenInstance(sender); - const address = token.address; - const metadata = await this.wallet.getContractMetadata(address); - if (metadata.isContractPublished) { - this.log.info(`Token at ${address.toString()} already deployed, refueling before setup`); - await this.ensureFeeJuiceBalance(sender, token); - } - return this.setupToken(sender); - } - - /** - * Setup token0 for AMM with refuel-first behaviour when token already exists. - */ - private async setupTokenContractWithOptionalEarlyRefuel( - deployer: AztecAddress, - salt: Fr, - name: string, - ticker: string, - decimals = 18, - ): Promise { - const deploy = TokenContract.deploy(this.wallet, deployer, name, ticker, decimals, { salt, universalDeploy: true }); - const instance = await deploy.getInstance(); - const metadata = await this.wallet.getContractMetadata(instance.address); - if (metadata.isContractPublished) { - this.log.info(`Token ${name} at ${instance.address.toString()} already deployed, refueling before setup`); - const token = TokenContract.at(instance.address, this.wallet); - await this.ensureFeeJuiceBalance(deployer, token); - } - return this.setupTokenContract(deployer, salt, name, ticker, decimals); - } - - private async getTokenInstance(sender: AztecAddress): Promise { - const salt = this.config.tokenSalt; - if (this.config.contract === SupportedTokenContracts.TokenContract) { - const deploy = TokenContract.deploy(this.wallet, sender, 'BotToken', 'BOT', 18, { salt, universalDeploy: true }); - const instance = await deploy.getInstance(); - return TokenContract.at(instance.address, this.wallet); - } - if (this.config.contract === SupportedTokenContracts.PrivateTokenContract) { - const tokenSecretKey = Fr.random(); - const tokenPublicKeys = (await deriveKeys(tokenSecretKey)).publicKeys; - const deploy = PrivateTokenContract.deploy(this.wallet, MINT_BALANCE, sender, { - salt, - universalDeploy: true, - publicKeys: tokenPublicKeys, - }); - const instance = await deploy.getInstance(); - return PrivateTokenContract.at(instance.address, this.wallet); - } - throw new Error(`Unsupported token contract type: ${this.config.contract}`); + private async setupAccountWithPrivateKey(secret: Fr) { + const salt = this.config.senderSalt ?? Fr.ONE; + const signingKey = deriveSigningKey(secret); + const accountManager = await this.wallet.createSchnorrInitializerlessAccount(secret, salt, signingKey); + return accountManager.address; } /** @@ -511,66 +418,39 @@ export class BotFactory { if (metadata.isContractPublished) { this.log.info(`Contract ${name} at ${address.toString()} already deployed`); await deploy.register(); - } else { - const sender = deployOpts.from === NO_FROM ? undefined : deployOpts.from; - const balance = sender ? await getFeeJuiceBalance(sender, this.aztecNode) : 0n; - const useClaim = - sender && - balance < FEE_JUICE_TOP_UP_THRESHOLD && - this.config.feePaymentMethod === 'fee_juice' && - !!this.config.l1RpcUrls?.length; - const mnemonicOrPrivateKey = this.config.l1PrivateKey?.getValue() ?? this.config.l1Mnemonic?.getValue(); - - if (useClaim && mnemonicOrPrivateKey) { - const claim = await this.getOrCreateBridgeClaim(sender!); - const paymentMethod = new FeeJuicePaymentMethodWithClaim(sender!, claim); - const { estimatedGas } = await deploy.simulate({ ...deployOpts, fee: { estimateGas: true, paymentMethod } }); - const maxFeesPerGas = (await this.getMinFees()).mul(1 + this.config.minFeePadding); - const gasSettings = GasSettings.from({ - ...estimatedGas!, - maxFeesPerGas, - maxPriorityFeesPerGas: GasFees.empty(), - }); - await this.withNoMinTxsPerBlock(async () => { - const { txHash } = await deploy.send({ ...deployOpts, fee: { gasSettings, paymentMethod }, wait: NO_WAIT }); - this.log.info( - `Sent contract ${name} deploy tx ${txHash.toString()} (using bridge claim, balance was ${balance})`, - ); - return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); - }); - await this.store.deleteBridgeClaim(sender!); - } else { - const { estimatedGas } = await deploy.simulate({ ...deployOpts, fee: { estimateGas: true } }); - this.log.info(`Deploying contract ${name} at ${address.toString()}`, { estimatedGas }); - await this.withNoMinTxsPerBlock(async () => { - const { txHash } = await deploy.send({ ...deployOpts, fee: { gasSettings: estimatedGas }, wait: NO_WAIT }); - this.log.info(`Sent contract ${name} setup tx with hash ${txHash.toString()}`); - return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); - }); - } + return instance; } + + // Setup always runs ensureFeeJuiceBalance before any deploy, so the account pays from its standing + // balance here. No manual gas estimation: the embedded wallet simulates before sending and derives + // the gas limits and padded maxFeesPerGas itself. + this.log.info(`Deploying contract ${name} at ${address.toString()}`); + await this.withNoMinTxsPerBlock(async () => { + const { txHash } = await deploy.send({ ...deployOpts, wait: NO_WAIT }); + this.log.info(`Sent contract ${name} deploy tx ${txHash.toString()}`); + return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); + }); + return instance; } + /** True when the config allows bridging fee juice from L1 (fee_juice mode, an L1 RPC, and an L1 key). */ + private isL1BridgingConfigured(): boolean { + const mnemonicOrPrivateKey = this.config.l1PrivateKey?.getValue() ?? this.config.l1Mnemonic?.getValue(); + return this.config.feePaymentMethod === 'fee_juice' && !!this.config.l1RpcUrls?.length && !!mnemonicOrPrivateKey; + } + /** - * Mints private and public tokens for the sender if their balance is below the minimum. - * @param token - Token contract. - */ - /** - * Ensures the account has sufficient fee juice by bridging from L1 if balance is below threshold. - * Bridges repeatedly until balance reaches the target (10k FJ). - * Used on startup/restart to top up when the account has run out after previous runs. + * Ensures the account holds enough fee juice before any other setup step. The account starts empty + * (initializerless accounts have no deployment tx) and the runtime loop pays fees from this balance and + * never refuels itself, so every flow funds the account up front. Bridges claims from L1 and consumes + * each with a claim-only tx until the balance clears the threshold, working from a zero (fresh run) or + * drained (restart) balance. Each bridge mints a fixed amount well above the threshold, so this is a + * single bridge in practice. No-op when L1 bridging is not configured or the balance is already above + * the threshold. */ - private async ensureFeeJuiceBalance( - account: AztecAddress, - token: TokenContract | PrivateTokenContract, - ): Promise { - const { feePaymentMethod, l1RpcUrls } = this.config; - if (feePaymentMethod !== 'fee_juice' || !l1RpcUrls?.length) { - return; - } - const mnemonicOrPrivateKey = this.config.l1PrivateKey?.getValue() ?? this.config.l1Mnemonic?.getValue(); - if (!mnemonicOrPrivateKey) { + private async ensureFeeJuiceBalance(account: AztecAddress): Promise { + if (!this.isL1BridgingConfigured()) { return; } @@ -580,36 +460,21 @@ export class BotFactory { return; } - this.log.info( - `Fee juice balance ${balance} below threshold ${FEE_JUICE_TOP_UP_THRESHOLD}, bridging from L1 until ${FEE_JUICE_TOP_UP_TARGET}`, - ); - const maxFeesPerGas = (await this.getMinFees()).mul(1 + this.config.minFeePadding); - const minimalInteraction = isStandardTokenContract(token) - ? token.methods.transfer_in_public(account, account, 0n, 0) - : token.methods.transfer(0n, account, account); + this.log.info(`Fee juice balance ${balance} below threshold ${FEE_JUICE_TOP_UP_THRESHOLD}, bridging from L1`); - while (balance < FEE_JUICE_TOP_UP_TARGET) { - const claim = await this.bridgeL1FeeJuice(account); + while (balance < FEE_JUICE_TOP_UP_THRESHOLD) { + // Persist the claim before consuming it: if the top-up tx fails or the bot crashes mid-loop, the + // next run reuses the pending claim instead of bridging again (and wasting the bridged funds). + const claim = await this.getOrCreateBridgeClaim(account); const paymentMethod = new FeeJuicePaymentMethodWithClaim(account, claim); - const { estimatedGas } = await minimalInteraction.simulate({ - from: account, - fee: { estimateGas: true, paymentMethod }, - }); - const gasSettings = GasSettings.from({ - ...estimatedGas!, - maxFeesPerGas, - maxPriorityFeesPerGas: GasFees.empty(), - }); await this.withNoMinTxsPerBlock(async () => { - const { txHash } = await minimalInteraction.send({ - from: account, - fee: { gasSettings, paymentMethod }, - wait: NO_WAIT, - }); + const executionPayload = await paymentMethod.getExecutionPayload(); + const { txHash } = await this.wallet.sendTx(executionPayload, { from: account, wait: NO_WAIT }); this.log.info(`Sent fee juice top-up tx ${txHash.toString()}`); return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds }); }); + await this.store.deleteBridgeClaim(account); balance = await getFeeJuiceBalance(account, this.aztecNode); this.log.info(`Fee juice balance after top-up: ${balance}`); } @@ -661,17 +526,14 @@ export class BotFactory { } /** - * Gets or creates a bridge claim for the recipient. - * Checks if a claim already exists in the store and reuses it if valid. - * Only creates a new bridge if fee juice balance is below threshold. + * Returns a usable bridge claim for the recipient, reusing a persisted one when its L1→L2 message is + * still available (resuming a top-up that failed or crashed before the claim was consumed) and bridging + * a fresh claim otherwise. The caller deletes the claim from the store once it has been consumed. */ private async getOrCreateBridgeClaim(recipient: AztecAddress): Promise { - // Check if we have an existing claim in the store const existingClaim = await this.store.getBridgeClaim(recipient); if (existingClaim) { this.log.info(`Found existing bridge claim for ${recipient.toString()}, checking validity...`); - - // Check if the message is ready on L2 try { const messageHash = Fr.fromHexString(existingClaim.claim.messageHash); await this.withNoMinTxsPerBlock(() => @@ -688,7 +550,6 @@ export class BotFactory { const claim = await this.bridgeL1FeeJuice(recipient); await this.store.saveBridgeClaim(recipient, claim); - return claim; } @@ -723,19 +584,6 @@ export class BotFactory { return claim as L2AmountClaim; } - /** Returns worst-case min fees across predicted slots, with fallback to current min fees. */ - private async getMinFees(): Promise { - try { - const predicted = await this.aztecNode.getPredictedMinFees(ManaUsageEstimate.Limit); - if (predicted.length === 0) { - return this.aztecNode.getCurrentMinFees(); - } - return predicted.reduce((worst, fees) => (fees.feePerL2Gas > worst.feePerL2Gas ? fees : worst)); - } catch { - return this.aztecNode.getCurrentMinFees(); - } - } - private async withNoMinTxsPerBlock(fn: () => Promise): Promise { if (!this.aztecNodeAdmin || !this.config.flushSetupTransactions) { this.log.verbose(`No node admin client or flushing not requested (not setting minTxsPerBlock to 0)`); 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 89edf39ed736..6088164782b8 100644 --- a/yarn-project/end-to-end/src/e2e_bot.test.ts +++ b/yarn-project/end-to-end/src/e2e_bot.test.ts @@ -3,7 +3,6 @@ import { NO_FROM } from '@aztec/aztec.js/account'; import { Fr } from '@aztec/aztec.js/fields'; import type { AztecNode } from '@aztec/aztec.js/node'; import { MinedTxReceipt, type TxReceipt } from '@aztec/aztec.js/tx'; -import { DeployAccountMethod } from '@aztec/aztec.js/wallet'; import type { CheatCodes } from '@aztec/aztec/testing'; import { AmmBot, @@ -151,19 +150,19 @@ describe('e2e_bot', () => { }; { - using deploy = jest.spyOn(DeployAccountMethod.prototype, 'send'); - - deploy.mockImplementation(() => { + using sendTx = jest.spyOn(EmbeddedWallet.prototype, 'sendTx'); + // Fail the fee juice top-up tx, which runs after the bridge claim has been persisted. + sendTx.mockImplementation(() => { throw new Error('test error'); }); await expect(Bot.create(config, wallet, aztecNode, aztecNodeAdmin, store)).rejects.toThrow('test error'); - expect(deploy).toHaveBeenCalledOnce(); expect(saveSpy).toHaveBeenCalledOnce(); } { saveSpy.mockClear(); + // The persisted claim is reused for the top-up, so no new claim is bridged or saved. await expect(Bot.create(config, wallet, aztecNode, aztecNodeAdmin, store)).resolves.toBeDefined(); expect(saveSpy).not.toHaveBeenCalled(); } @@ -193,8 +192,8 @@ describe('e2e_bot', () => { }; { - using deploy = jest.spyOn(DeployAccountMethod.prototype, 'send'); - deploy.mockImplementation(() => { + using sendTx = jest.spyOn(EmbeddedWallet.prototype, 'sendTx'); + sendTx.mockImplementation(() => { throw new Error('test error'); }); await expect(Bot.create(config, wallet, aztecNode, aztecNodeAdmin, store)).rejects.toThrow('test error'); @@ -203,7 +202,8 @@ describe('e2e_bot', () => { { saveSpy.mockClear(); - // same private key, but different salt derives a different L2 address + // same private key, but different salt derives a different L2 address, so the persisted claim does + // not apply and a fresh claim is bridged and saved config.senderSalt = config.senderSalt!.add(Fr.ONE); await expect(Bot.create(config, wallet, aztecNode, aztecNodeAdmin, store)).resolves.toBeDefined(); expect(saveSpy).toHaveBeenCalledOnce(); diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index 65e15e61dd90..56e3d2e3acb0 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -1,6 +1,7 @@ import { type Account, NO_FROM } from '@aztec/aztec.js/account'; import { CallAuthorizationRequest } from '@aztec/aztec.js/authorization'; import { + ContractFunctionInteraction, type InteractionWaitOptions, NO_WAIT, type SendReturn, @@ -19,6 +20,8 @@ import type { import { AccountManager, TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; import { DefaultEntrypoint } from '@aztec/entrypoints/default'; +import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; +import { Schnorr } from '@aztec/foundation/crypto/schnorr'; import { Fq, Fr } from '@aztec/foundation/curves/bn254'; import type { Logger } from '@aztec/foundation/log'; import type { AztecAsyncKVStore } from '@aztec/kv-store'; @@ -389,6 +392,8 @@ export class EmbeddedWallet extends BaseWallet { signingKey: Buffer, ): Promise { let contract; + let immutablesHash; + let publicKey; switch (type) { case 'schnorr': { contract = await this.accountContracts.getSchnorrAccountContract(Fq.fromBuffer(signingKey)); @@ -396,6 +401,8 @@ export class EmbeddedWallet extends BaseWallet { } case 'schnorr_initializerless': { contract = await this.accountContracts.getSchnorrInitializerlessAccountContract(Fq.fromBuffer(signingKey)); + publicKey = await new Schnorr().computePublicKey(Fq.fromBuffer(signingKey)); + immutablesHash = await poseidon2Hash([publicKey.x, publicKey.y]); break; } case 'ecdsasecp256k1': { @@ -411,17 +418,29 @@ export class EmbeddedWallet extends BaseWallet { } } - const accountManager = await AccountManager.create(this, secret, contract, { salt }); + const accountManager = await AccountManager.create(this, secret, contract, { salt, immutablesHash }); const instance = accountManager.getInstance(); const existingInstance = await this.pxe.getContractInstance(instance.address); if (!existingInstance) { const existingArtifact = await this.pxe.getContractArtifact(instance.currentContractClassId); + const artifact = existingArtifact ?? (await accountManager.getAccountContract().getContractArtifact()); await this.registerContract( instance, !existingArtifact ? await accountManager.getAccountContract().getContractArtifact() : undefined, accountManager.getSecretKey(), ); + if (type === 'schnorr_initializerless') { + const constructor = artifact.functions.find(f => f.name === 'constructor'); + if (!constructor) { + throw new Error('Could not create SchnorrInitializerlessAccountContract: constructor ABI not found'); + } + const storeCall = new ContractFunctionInteraction(this, instance.address, constructor, [ + publicKey!.x, + publicKey!.y, + ]); + await storeCall.simulate({ from: instance.address }); + } } return accountManager; } @@ -443,6 +462,11 @@ export class EmbeddedWallet extends BaseWallet { return this.createAndStoreAccount(alias ?? '', 'schnorr', secret, salt, sk.toBuffer()); } + createSchnorrInitializerlessAccount(secret: Fr, salt: Fr, signingKey?: Fq, alias?: string): Promise { + const sk = signingKey ?? deriveSigningKey(secret); + return this.createAndStoreAccount(alias ?? '', 'schnorr_initializerless', secret, salt, sk.toBuffer()); + } + createECDSARAccount(secret: Fr, salt: Fr, signingKey: Buffer, alias?: string): Promise { return this.createAndStoreAccount(alias ?? '', 'ecdsasecp256r1', secret, salt, signingKey); } From 12e8b0912c5c11106ac5d57333c1f8ffd18b8d9a Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 9 Jun 2026 21:51:34 +0200 Subject: [PATCH 07/23] comments from review --- .../schnorr_initializerless_account_contract/src/main.nr | 4 ++-- yarn-project/accounts/src/ecdsa/ecdsa_k/lazy.ts | 2 +- yarn-project/accounts/src/ecdsa/ecdsa_r/lazy.ts | 2 +- yarn-project/accounts/src/schnorr/initializerless/lazy.ts | 2 +- yarn-project/accounts/src/schnorr/private_immutable/lazy.ts | 2 +- .../src/scripts/generate_client_artifacts_helper.ts | 4 ++-- yarn-project/protocol-contracts/src/class-registry/lazy.ts | 2 +- yarn-project/protocol-contracts/src/fee-juice/lazy.ts | 2 +- yarn-project/protocol-contracts/src/instance-registry/lazy.ts | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr index a02b1917a635..cf49229cf20b 100644 --- a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr @@ -40,7 +40,7 @@ pub contract SchnorrInitializerlessAccount { unconstrained fn constructor(public_key: PublicKey) { let serialized_pub_key = public_key.serialize(); let expected = poseidon2_hash(public_key.serialize()); - let instance = aztec::oracle::get_contract_instance::get_contract_instance(self.address); + let instance = get_contract_instance(self.address); assert_eq( expected, instance.immutables_hash, @@ -70,7 +70,7 @@ pub contract SchnorrInitializerlessAccount { let public_key = unsafe { load(context.this_address(), PUB_KEY_SLOT, context.this_address()) } .map(|data| PublicKey::deserialize(data)) .unwrap_or_else(|| panic( - "Public key was not stored in this PXE. Please call `constructor` first", + "Public key was not stored in the Private eXecution Environment. Please call `constructor` first", )); let expected = poseidon2_hash(public_key.serialize()); diff --git a/yarn-project/accounts/src/ecdsa/ecdsa_k/lazy.ts b/yarn-project/accounts/src/ecdsa/ecdsa_k/lazy.ts index b2f3a3e21cfa..27618dcb67df 100644 --- a/yarn-project/accounts/src/ecdsa/ecdsa_k/lazy.ts +++ b/yarn-project/accounts/src/ecdsa/ecdsa_k/lazy.ts @@ -16,7 +16,7 @@ import { EcdsaKBaseAccountContract } from './account_contract.js'; export async function getEcdsaKAccountContractArtifact() { // Cannot assert this import as it's incompatible with bundlers like vite // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with + // Even if now supported by all major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS const { default: ecdsaKAccountContractJson } = await import('../../../artifacts/EcdsaKAccount.json'); diff --git a/yarn-project/accounts/src/ecdsa/ecdsa_r/lazy.ts b/yarn-project/accounts/src/ecdsa/ecdsa_r/lazy.ts index f2cb695043a1..7347c400850b 100644 --- a/yarn-project/accounts/src/ecdsa/ecdsa_r/lazy.ts +++ b/yarn-project/accounts/src/ecdsa/ecdsa_r/lazy.ts @@ -16,7 +16,7 @@ import { EcdsaRBaseAccountContract } from './account_contract.js'; export async function getEcdsaRAccountContractArtifact() { // Cannot assert this import as it's incompatible with bundlers like vite // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with + // Even if now supported by all major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS const { default: ecdsaKAccountContractJson } = await import('../../../artifacts/EcdsaRAccount.json'); diff --git a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts index cd7b48edbf3a..2627078dc230 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts @@ -21,7 +21,7 @@ import { SchnorrBaseAccountContract } from '../account_contract.js'; export async function getSchnorrInitializerlessAccountContractArtifact() { // Cannot assert this import as it's incompatible with bundlers like vite // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with + // Even if now supported by all major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrInitializerlessAccount.json'); diff --git a/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts b/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts index a50f5b083db2..468354a29d8d 100644 --- a/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts +++ b/yarn-project/accounts/src/schnorr/private_immutable/lazy.ts @@ -21,7 +21,7 @@ import { SchnorrBaseAccountContract } from '../account_contract.js'; export async function getSchnorrAccountContractArtifact() { // Cannot assert this import as it's incompatible with bundlers like vite // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with + // Even if now supported by all major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS const { default: schnorrAccountContractJson } = await import('../../../artifacts/SchnorrAccount.json'); diff --git a/yarn-project/noir-protocol-circuits-types/src/scripts/generate_client_artifacts_helper.ts b/yarn-project/noir-protocol-circuits-types/src/scripts/generate_client_artifacts_helper.ts index 42e67b0d8882..f85453dd869f 100644 --- a/yarn-project/noir-protocol-circuits-types/src/scripts/generate_client_artifacts_helper.ts +++ b/yarn-project/noir-protocol-circuits-types/src/scripts/generate_client_artifacts_helper.ts @@ -68,7 +68,7 @@ function generateCircuitArtifactImportFunction() { .map(artifactName => { // Cannot assert this import as it's incompatible with bundlers like vite // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with + // Even if now supported by all major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS return `case '${artifactName}': { @@ -106,7 +106,7 @@ function generateVkImportFunction() { const cases = Object.values(ClientCircuitArtifactNames).map(artifactName => { // Cannot assert this import as it's incompatible with bundlers like vite // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with + // Even if now supported by all major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS return `case '${artifactName}': { diff --git a/yarn-project/protocol-contracts/src/class-registry/lazy.ts b/yarn-project/protocol-contracts/src/class-registry/lazy.ts index 6148d927b79f..17ea9781d3c6 100644 --- a/yarn-project/protocol-contracts/src/class-registry/lazy.ts +++ b/yarn-project/protocol-contracts/src/class-registry/lazy.ts @@ -12,7 +12,7 @@ export async function getContractClassRegistryArtifact(): Promise { if (!protocolContractArtifact) { // Cannot assert this import as it's incompatible with bundlers like vite // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - // Even if now supported by al major browsers, the MIME type is replaced with + // Even if now supported by all major browsers, the MIME type is replaced with // "text/javascript" // In the meantime, this lazy import is INCOMPATIBLE WITH NODEJS const { default: feeJuiceJson } = await import('../../artifacts/FeeJuice.json'); diff --git a/yarn-project/protocol-contracts/src/instance-registry/lazy.ts b/yarn-project/protocol-contracts/src/instance-registry/lazy.ts index 31f554c1d061..bb3bc7aa3cbb 100644 --- a/yarn-project/protocol-contracts/src/instance-registry/lazy.ts +++ b/yarn-project/protocol-contracts/src/instance-registry/lazy.ts @@ -13,7 +13,7 @@ export async function getContractInstanceRegistryArtifact(): Promise Date: Wed, 10 Jun 2026 12:31:48 +0200 Subject: [PATCH 08/23] migrated most tests --- .../src/schnorr/initializerless/index.ts | 6 +- .../src/schnorr/initializerless/lazy.ts | 5 +- yarn-project/accounts/src/testing/index.ts | 6 +- yarn-project/accounts/src/testing/lazy.ts | 6 +- .../aztec.js/src/account/account_contract.ts | 10 +- yarn-project/aztec/src/examples/token.ts | 6 +- .../aztec/src/local-network/local-network.ts | 4 +- yarn-project/bot/src/factory.ts | 11 +- .../cmds/infrastructure/setup_l2_contract.ts | 10 +- .../client_flows/client_flows_benchmark.ts | 13 +- .../src/composed/ha/e2e_ha_full.test.ts | 3 +- .../end-to-end/src/e2e_authwit.test.ts | 8 +- .../end-to-end/src/e2e_avm_simulator.test.ts | 3 +- .../blacklist_token_contract_test.ts | 15 +- yarn-project/end-to-end/src/e2e_bot.test.ts | 7 +- .../cross_chain_messaging_test.ts | 11 +- .../end-to-end/src/e2e_custom_message.test.ts | 3 +- .../src/e2e_deploy_contract/deploy_test.ts | 7 +- .../end-to-end/src/e2e_event_logs.test.ts | 5 +- .../end-to-end/src/e2e_event_only.test.ts | 3 +- .../end-to-end/src/e2e_fees/fees_test.ts | 14 +- .../src/e2e_genesis_timestamp.test.ts | 2 +- .../src/e2e_lending_contract.test.ts | 3 +- .../e2e_multi_validator_node.test.ts | 3 +- .../nested_contract_test.ts | 11 +- .../end-to-end/src/e2e_p2p/add_rollup.test.ts | 10 +- .../end-to-end/src/e2e_p2p/p2p_network.ts | 1 - .../src/e2e_sequencer_config.test.ts | 9 +- .../e2e_token_contract/token_contract_test.ts | 10 +- .../src/fixtures/e2e_prover_test.ts | 19 +-- yarn-project/end-to-end/src/fixtures/setup.ts | 156 ++++-------------- yarn-project/end-to-end/src/fixtures/utils.ts | 4 +- .../forward-compatibility/wallet_service.ts | 4 +- .../end-to-end/src/shared/uniswap_l1_l2.ts | 7 +- .../end-to-end/src/test-wallet/test_wallet.ts | 37 ++++- yarn-project/wallets/src/testing.ts | 28 ++-- 36 files changed, 175 insertions(+), 285 deletions(-) diff --git a/yarn-project/accounts/src/schnorr/initializerless/index.ts b/yarn-project/accounts/src/schnorr/initializerless/index.ts index e9d467160a8c..11a92100f324 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/index.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/index.ts @@ -6,6 +6,7 @@ */ import { getAccountContractAddress } from '@aztec/aztec.js/account'; import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { ContractArtifact } from '@aztec/stdlib/abi'; @@ -48,5 +49,8 @@ export async function getSchnorrInitializerlessAccountContractAddress( ): Promise { const signingKey = signingPrivateKey ?? deriveSigningKey(secret); const accountContract = new SchnorrInitializerlessAccountContract(signingKey); - return await getAccountContractAddress(accountContract, secret, salt); + // The initializerless address commits to the public keys via the immutables hash, threaded like salt. + const { constructorArgs } = (await accountContract.getInitializationFunctionAndArgs())!; + const immutablesHash = await poseidon2Hash(constructorArgs); + return await getAccountContractAddress(accountContract, secret, salt, immutablesHash); } diff --git a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts index 2627078dc230..61b49716c0da 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts @@ -5,6 +5,7 @@ * @packageDocumentation */ import { getAccountContractAddress } from '@aztec/aztec.js/account'; +import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { ContractArtifact } from '@aztec/stdlib/abi'; @@ -56,5 +57,7 @@ export async function getSchnorrInitializerlessAccountContractAddress( ): Promise { const signingKey = signingPrivateKey ?? deriveSigningKey(secret); const accountContract = new SchnorrInitializerlessAccountContract(signingKey); - return await getAccountContractAddress(accountContract, secret, salt); + const { constructorArgs } = (await accountContract.getInitializationFunctionAndArgs())!; + const immutablesHash = await poseidon2Hash(constructorArgs); + return await getAccountContractAddress(accountContract, secret, salt, immutablesHash); } diff --git a/yarn-project/accounts/src/testing/index.ts b/yarn-project/accounts/src/testing/index.ts index 549e99311797..0804bb660493 100644 --- a/yarn-project/accounts/src/testing/index.ts +++ b/yarn-project/accounts/src/testing/index.ts @@ -6,7 +6,7 @@ import { Fr } from '@aztec/aztec.js/fields'; import { deriveSigningKey } from '@aztec/stdlib/keys'; -import { getSchnorrAccountContractAddress } from '../schnorr/private_immutable/index.js'; +import { getSchnorrInitializerlessAccountContractAddress } from '../schnorr/initializerless/index.js'; import { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_ENCRYPTION_KEYS, @@ -32,7 +32,7 @@ export function getInitialTestAccountsData(): Promise { secret, signingKey: INITIAL_TEST_ENCRYPTION_KEYS[i], salt: INITIAL_TEST_ACCOUNT_SALTS[i], - address: await getSchnorrAccountContractAddress( + address: await getSchnorrInitializerlessAccountContractAddress( secret, INITIAL_TEST_ACCOUNT_SALTS[i], INITIAL_TEST_SIGNING_KEYS[i], @@ -53,7 +53,7 @@ export async function generateSchnorrAccounts(numberOfAccounts: number): Promise secret, signingKey: deriveSigningKey(secret), salt, - address: await getSchnorrAccountContractAddress(secret, salt), + address: await getSchnorrInitializerlessAccountContractAddress(secret, salt), }; }), ); diff --git a/yarn-project/accounts/src/testing/lazy.ts b/yarn-project/accounts/src/testing/lazy.ts index 94a27eb6fc1b..8bba3f235d60 100644 --- a/yarn-project/accounts/src/testing/lazy.ts +++ b/yarn-project/accounts/src/testing/lazy.ts @@ -6,7 +6,7 @@ import { Fr } from '@aztec/aztec.js/fields'; import { deriveSigningKey } from '@aztec/stdlib/keys'; -import { getSchnorrAccountContractAddress } from '../schnorr/private_immutable/lazy.js'; +import { getSchnorrInitializerlessAccountContractAddress } from '../schnorr/initializerless/lazy.js'; import { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_ENCRYPTION_KEYS, @@ -26,7 +26,7 @@ export function getInitialTestAccountsData(): Promise { secret, signingKey: INITIAL_TEST_ENCRYPTION_KEYS[i], salt: INITIAL_TEST_ACCOUNT_SALTS[i], - address: await getSchnorrAccountContractAddress( + address: await getSchnorrInitializerlessAccountContractAddress( secret, INITIAL_TEST_ACCOUNT_SALTS[i], INITIAL_TEST_SIGNING_KEYS[i], @@ -47,7 +47,7 @@ export async function generateSchnorrAccounts(numberOfAccounts: number): Promise secret, signingKey: deriveSigningKey(secret), salt, - address: await getSchnorrAccountContractAddress(secret, salt), + address: await getSchnorrInitializerlessAccountContractAddress(secret, salt), }; }), ); diff --git a/yarn-project/aztec.js/src/account/account_contract.ts b/yarn-project/aztec.js/src/account/account_contract.ts index 384ba8ab8642..44802e041240 100644 --- a/yarn-project/aztec.js/src/account/account_contract.ts +++ b/yarn-project/aztec.js/src/account/account_contract.ts @@ -47,9 +47,14 @@ export interface AccountContract { } /** - * Compute the address of an account contract from secret and salt. + * Compute the address of an account contract from secret, salt and optional immutables hash */ -export async function getAccountContractAddress(accountContract: AccountContract, secret: Fr, salt: Fr) { +export async function getAccountContractAddress( + accountContract: AccountContract, + secret: Fr, + salt: Fr, + immutablesHash?: Fr, +) { const { publicKeys } = await deriveKeys(secret); const { constructorName, constructorArgs } = (await accountContract.getInitializationFunctionAndArgs()) ?? { constructorName: undefined, @@ -61,6 +66,7 @@ export async function getAccountContractAddress(accountContract: AccountContract constructorArgs, salt, publicKeys, + immutablesHash, }); return instance.address; } diff --git a/yarn-project/aztec/src/examples/token.ts b/yarn-project/aztec/src/examples/token.ts index 1e7e2077910d..fddb6704492f 100644 --- a/yarn-project/aztec/src/examples/token.ts +++ b/yarn-project/aztec/src/examples/token.ts @@ -21,10 +21,10 @@ async function main() { const wallet = await EmbeddedWallet.create(node); - // During local network setup we deploy a few accounts. Below we add them to our wallet. + // During local network setup we create a few initializerless accounts. Below we add them to our wallet. const [aliceInitialAccountData, bobInitialAccountData] = await getInitialTestAccountsData(); - await wallet.createSchnorrAccount(aliceInitialAccountData.secret, aliceInitialAccountData.salt); - await wallet.createSchnorrAccount(bobInitialAccountData.secret, bobInitialAccountData.salt); + await wallet.createSchnorrInitializerlessAccount(aliceInitialAccountData.secret, aliceInitialAccountData.salt); + await wallet.createSchnorrInitializerlessAccount(bobInitialAccountData.secret, bobInitialAccountData.salt); const alice = aliceInitialAccountData.address; const bob = bobInitialAccountData.address; diff --git a/yarn-project/aztec/src/local-network/local-network.ts b/yarn-project/aztec/src/local-network/local-network.ts index a231116bec94..853b1a199233 100644 --- a/yarn-project/aztec/src/local-network/local-network.ts +++ b/yarn-project/aztec/src/local-network/local-network.ts @@ -30,7 +30,7 @@ import { initTelemetryClient, } from '@aztec/telemetry-client'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; -import { deployFundedSchnorrAccounts } from '@aztec/wallets/testing'; +import { createFundedInitializerlessAccounts } from '@aztec/wallets/testing'; import { getGenesisValues } from '@aztec/world-state/testing'; import { type Hex, createPublicClient, fallback, http as httpViemTransport } from 'viem'; @@ -250,7 +250,7 @@ export async function createLocalNetwork(config: Partial = { }); userLog('Setting up funded test accounts...'); - const accountManagers = await deployFundedSchnorrAccounts(wallet, initialAccounts, setupWaitOpts); + const accountManagers = await createFundedInitializerlessAccounts(wallet, initialAccounts); const accLogs = await createAccountLogs(accountManagers, wallet); userLog(accLogs.join('')); diff --git a/yarn-project/bot/src/factory.ts b/yarn-project/bot/src/factory.ts index fc8a6e26140a..788dc69de8c3 100644 --- a/yarn-project/bot/src/factory.ts +++ b/yarn-project/bot/src/factory.ts @@ -202,14 +202,15 @@ export class BotFactory { } /** - * Keyless fallback for tests and local dev: reuses the first genesis test account, whose schnorr address - * is pre-funded with fee juice via `initialFundedAccounts`. It must be a (non-initializerless) schnorr - * account so the address matches the funded one. Production bots set a sender private key and fund the - * resulting initializerless account from L1 instead; see setupAccountWithPrivateKey. + * Keyless fallback for tests and local dev: reuses the first genesis test account, whose address is + * pre-funded with fee juice via `initialFundedAccounts`. The test accounts are initializerless, so this + * must create an initializerless account for the address to match the funded one. Production bots set a + * sender private key and fund the resulting initializerless account from L1 instead; see + * setupAccountWithPrivateKey. */ private async setupTestAccount() { const [initialAccountData] = await getInitialTestAccountsData(); - const accountManager = await this.wallet.createSchnorrAccount( + const accountManager = await this.wallet.createSchnorrInitializerlessAccount( initialAccountData.secret, initialAccountData.salt, initialAccountData.signingKey, diff --git a/yarn-project/cli/src/cmds/infrastructure/setup_l2_contract.ts b/yarn-project/cli/src/cmds/infrastructure/setup_l2_contract.ts index bd74d49765e9..0625ca237bc2 100644 --- a/yarn-project/cli/src/cmds/infrastructure/setup_l2_contract.ts +++ b/yarn-project/cli/src/cmds/infrastructure/setup_l2_contract.ts @@ -8,7 +8,7 @@ import { jsonStringify } from '@aztec/foundation/json-rpc'; import type { LogFn } from '@aztec/foundation/log'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; -import { deployFundedSchnorrAccounts } from '@aztec/wallets/testing'; +import { createFundedInitializerlessAccounts } from '@aztec/wallets/testing'; export async function setupL2Contracts(nodeUrl: string, testAccounts: boolean, json: boolean, log: LogFn) { const waitOpts: WaitOpts = { @@ -23,16 +23,16 @@ export async function setupL2Contracts(nodeUrl: string, testAccounts: boolean, j const node = createAztecNodeClient(nodeUrl); const wallet = await EmbeddedWallet.create(node); - let deployedAccountManagers: AccountManager[] = []; + let accountManagers: AccountManager[] = []; if (testAccounts) { - log('setupL2Contracts: Deploying test accounts...'); + log('setupL2Contracts: Creating test accounts...'); const initialAccountsData = await getInitialTestAccountsData(); - deployedAccountManagers = await deployFundedSchnorrAccounts(wallet, initialAccountsData, waitOpts); + accountManagers = await createFundedInitializerlessAccounts(wallet, initialAccountsData); } if (json) { const toPrint: Record = { ...ProtocolContractAddress }; - deployedAccountManagers.forEach((a, i) => { + accountManagers.forEach((a, i) => { toPrint[`testAccount${i}`] = a.address; }); log(JSON.stringify(toPrint, null, 2)); 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 74ba3a4439d7..0c325fc4e82a 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 @@ -29,7 +29,13 @@ import { GasSettings } from '@aztec/stdlib/gas'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { AUTOMINE_E2E_OPTS, MNEMONIC, getPaddedMaxFeesPerGas } from '../../fixtures/fixtures.js'; -import { type EndToEndContext, type SetupOptions, deployAccounts, setup, teardown } from '../../fixtures/setup.js'; +import { + type EndToEndContext, + type SetupOptions, + createFundedAccounts, + setup, + teardown, +} from '../../fixtures/setup.js'; import { mintTokensToPrivate } from '../../fixtures/token_utils.js'; import { setupSponsoredFPC } from '../../fixtures/utils.js'; import { CrossChainTestHarness } from '../../shared/cross_chain_test_harness.js'; @@ -138,7 +144,6 @@ export class ClientFlowsBenchmark { this.context = await setup(0, { ...this.setupOptions, fundSponsoredFPC: true, - skipAccountDeployment: true, l1ContractsArgs: this.setupOptions, txPublicSetupAllowListExtend: [...(this.setupOptions.txPublicSetupAllowListExtend ?? []), ...tokenAllowList], }); @@ -203,7 +208,7 @@ export class ClientFlowsBenchmark { async applyInitialAccounts() { this.logger.info('Applying initial accounts setup'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( 2, this.logger, )({ @@ -211,7 +216,7 @@ export class ClientFlowsBenchmark { initialFundedAccounts: this.context.initialFundedAccounts, }); - const [{ address: adminAddress }, { address: sequencerAddress }] = deployedAccounts; + const [{ address: adminAddress }, { address: sequencerAddress }] = accounts; this.adminWallet = this.context.wallet; this.aztecNode = this.context.aztecNodeService; 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 d59e3cb189c9..4c1ab31367bf 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 @@ -157,7 +157,7 @@ describe('HA Full Setup', () => { deployL1ContractsValues, genesis, } = await setup( - 1, + 0, { ...PIPELINING_SETUP_OPTS, initialValidators, @@ -170,7 +170,6 @@ describe('HA Full Setup', () => { startProverNode: true, // Disable validation on this node disableValidator: true, - skipAccountDeployment: true, // Enable P2P for transaction gossip p2pEnabled: true, // Enable slashing for testing governance + slashing vote coordination diff --git a/yarn-project/end-to-end/src/e2e_authwit.test.ts b/yarn-project/end-to-end/src/e2e_authwit.test.ts index bc31ce0e99d2..546cd7fa60d6 100644 --- a/yarn-project/end-to-end/src/e2e_authwit.test.ts +++ b/yarn-project/end-to-end/src/e2e_authwit.test.ts @@ -10,12 +10,7 @@ import { jest } from '@jest/globals'; import { sendThroughAuthwitProxy } from './fixtures/authwit_proxy.js'; import { AUTOMINE_E2E_OPTS, DUPLICATE_NULLIFIER_ERROR } from './fixtures/fixtures.js'; -import { - type EndToEndContext, - ensureAccountContractsPublished, - ensureAuthRegistryPublished, - setup, -} from './fixtures/utils.js'; +import { type EndToEndContext, ensureAuthRegistryPublished, setup } from './fixtures/utils.js'; import type { TestWallet } from './test-wallet/test_wallet.js'; const TIMEOUT = 300_000; @@ -37,7 +32,6 @@ describe('e2e_authwit_tests', () => { wallet, accounts: [account1Address, account2Address], } = await setup(2, { ...AUTOMINE_E2E_OPTS })); - await ensureAccountContractsPublished(wallet, [account1Address, account2Address]); await ensureAuthRegistryPublished(wallet, account1Address); ({ contract: auth } = await AuthWitTestContract.deploy(wallet).send({ from: account1Address })); diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 9b344c1a424b..554e336ea56a 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -10,7 +10,7 @@ import { AvmTestContract } from '@aztec/noir-test-contracts.js/AvmTest'; import { jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; -import { ensureAccountContractsPublished, setup } from './fixtures/utils.js'; +import { setup } from './fixtures/utils.js'; const TIMEOUT = 600_000; @@ -29,7 +29,6 @@ describe('e2e_avm_simulator', () => { aztecNode, accounts: [defaultAccountAddress], } = await setup(1, { ...AUTOMINE_E2E_OPTS })); - await ensureAccountContractsPublished(wallet, [defaultAccountAddress]); }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts index 8065156fa0a8..8c6dab4e7dc2 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts @@ -17,9 +17,8 @@ import { jest } from '@jest/globals'; import { type EndToEndContext, type SetupOptions, - deployAccounts, + createFundedAccounts, ensureAuthRegistryPublished, - publicDeployAccounts, setup, teardown, } from '../fixtures/setup.js'; @@ -97,7 +96,7 @@ export class BlacklistTokenContractTest { jest.setTimeout(600_000); this.logger.info('Deploying 3 accounts'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( 3, this.logger, )({ @@ -109,15 +108,12 @@ export class BlacklistTokenContractTest { this.aztecNode = this.context.aztecNodeService; this.sequencer = this.context.sequencer!; this.wallet = this.context.wallet; - this.adminAddress = deployedAccounts[0].address; - this.otherAddress = deployedAccounts[1].address; - this.blacklistedAddress = deployedAccounts[2].address; + this.adminAddress = accounts[0].address; + this.otherAddress = accounts[1].address; + this.blacklistedAddress = accounts[2].address; this.logger.info('Setting up blacklist token contract'); await ensureAuthRegistryPublished(this.wallet, this.adminAddress); - // Create the token contract state. - this.logger.verbose(`Public deploy accounts...`); - await publicDeployAccounts(this.wallet, [this.adminAddress, this.otherAddress, this.blacklistedAddress]); this.logger.verbose(`Deploying TokenContract...`); ({ contract: this.asset } = await TokenBlacklistContract.deploy(this.wallet, this.adminAddress).send({ @@ -160,7 +156,6 @@ export class BlacklistTokenContractTest { this.context = await setup(0, { ...opts, fundSponsoredFPC: true, - skipAccountDeployment: true, }); await this.applyBaseSetup(); } 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 6088164782b8..3fa1e83524ae 100644 --- a/yarn-project/end-to-end/src/e2e_bot.test.ts +++ b/yarn-project/end-to-end/src/e2e_bot.test.ts @@ -1,5 +1,4 @@ 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 { MinedTxReceipt, type TxReceipt } from '@aztec/aztec.js/tx'; @@ -49,9 +48,9 @@ describe('e2e_bot', () => { config: { l1RpcUrls }, } = setupResult); 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: NO_FROM }); + // Initializerless test accounts need no deployment tx; creating registers it and the genesis funding + // at its address makes it immediately usable. + await wallet.createSchnorrInitializerlessAccount(botAccount.secret, botAccount.salt, botAccount.signingKey); }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts index df33b44eebab..8860f0a4b39e 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts @@ -26,9 +26,8 @@ import { MNEMONIC } from '../fixtures/fixtures.js'; import { type EndToEndContext, type SetupOptions, - deployAccounts, + createFundedAccounts, ensureAuthRegistryPublished, - publicDeployAccounts, setup, teardown, } from '../fixtures/setup.js'; @@ -100,7 +99,6 @@ export class CrossChainMessagingTest { ...this.setupOptions, ...opts, fundSponsoredFPC: true, - skipAccountDeployment: true, l1ContractsArgs: { ...this.deployL1ContractsArgs, ...opts.l1ContractsArgs }, }, { ...this.pxeOpts, ...pxeOpts }, @@ -160,22 +158,19 @@ export class CrossChainMessagingTest { // Deploy 3 accounts this.logger.info('Applying 3_accounts setup'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( 3, this.logger, )({ wallet: this.context.wallet, initialFundedAccounts: this.context.initialFundedAccounts, }); - [this.ownerAddress, this.user1Address, this.user2Address] = deployedAccounts.map(a => a.address); + [this.ownerAddress, this.user1Address, this.user2Address] = accounts.map(a => a.address); // Set up cross chain messaging this.logger.info('Applying e2e_cross_chain_messaging setup'); await ensureAuthRegistryPublished(this.wallet, this.ownerAddress); - // Create the token contract state. - this.logger.verbose(`Public deploy accounts...`); - await publicDeployAccounts(this.wallet, [this.ownerAddress, this.user1Address, this.user2Address]); this.l1Client = createExtendedL1Client(this.aztecNodeConfig.l1RpcUrls, MNEMONIC); diff --git a/yarn-project/end-to-end/src/e2e_custom_message.test.ts b/yarn-project/end-to-end/src/e2e_custom_message.test.ts index 34fa17f1c901..464894af2ee3 100644 --- a/yarn-project/end-to-end/src/e2e_custom_message.test.ts +++ b/yarn-project/end-to-end/src/e2e_custom_message.test.ts @@ -8,7 +8,7 @@ import { CustomMessageContract, type MultiLogEvent } from '@aztec/noir-test-cont import { jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; -import { ensureAccountContractsPublished, setup } from './fixtures/utils.js'; +import { setup } from './fixtures/utils.js'; const TIMEOUT = 300_000; @@ -26,7 +26,6 @@ describe('CustomMessage - Multi-Log Pattern', () => { wallet, accounts: [account], } = await setup(1, { ...AUTOMINE_E2E_OPTS })); - await ensureAccountContractsPublished(wallet, [account]); ({ contract } = await CustomMessageContract.deploy(wallet).send({ from: account })); }); diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts index aeaf3da2d6fe..fa214c4c181b 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts @@ -9,7 +9,7 @@ import type { Wallet } from '@aztec/aztec.js/wallet'; import type { StatefulTestContract } from '@aztec/noir-test-contracts.js/StatefulTest'; import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; -import { type EndToEndContext, type SetupOptions, deployAccounts, setup, teardown } from '../fixtures/setup.js'; +import { type EndToEndContext, type SetupOptions, createFundedAccounts, setup, teardown } from '../fixtures/setup.js'; import type { TestWallet } from '../test-wallet/test_wallet.js'; export class DeployTest { @@ -29,7 +29,6 @@ export class DeployTest { this.context = await setup(0, { ...opts, fundSponsoredFPC: true, - skipAccountDeployment: true, }); this.aztecNode = this.context.aztecNodeService; this.wallet = this.context.wallet; @@ -44,14 +43,14 @@ export class DeployTest { private async applyInitialAccount() { this.logger.info('Applying initial account setup'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( 1, this.logger, )({ wallet: this.context.wallet, initialFundedAccounts: this.context.initialFundedAccounts, }); - this.defaultAccountAddress = deployedAccounts[0].address; + this.defaultAccountAddress = accounts[0].address; } async registerContract( diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index 0afbd83f49f5..ce1410241cd8 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -18,7 +18,7 @@ import { import { jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; -import { ensureAccountContractsPublished, setup } from './fixtures/utils.js'; +import { setup } from './fixtures/utils.js'; const TIMEOUT = 300_000; @@ -44,9 +44,6 @@ describe('Logs', () => { logger: log, } = await setup(2, { ...AUTOMINE_E2E_OPTS })); - log.warn(`Setup complete, checking account contracts published`); - await ensureAccountContractsPublished(wallet, [account1Address, account2Address]); - log.warn(`Deploying test contract`); ({ contract: testLogContract } = await TestLogContract.deploy(wallet).send({ from: account1Address })); }); diff --git a/yarn-project/end-to-end/src/e2e_event_only.test.ts b/yarn-project/end-to-end/src/e2e_event_only.test.ts index dda2710f7d06..e2d0a79b7ef1 100644 --- a/yarn-project/end-to-end/src/e2e_event_only.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_only.test.ts @@ -7,7 +7,7 @@ import { EventOnlyContract, type TestEvent } from '@aztec/noir-test-contracts.js import { jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; -import { ensureAccountContractsPublished, setup } from './fixtures/utils.js'; +import { setup } from './fixtures/utils.js'; const TIMEOUT = 300_000; @@ -26,7 +26,6 @@ describe('EventOnly', () => { wallet, accounts: [defaultAccountAddress], } = await setup(1, { ...AUTOMINE_E2E_OPTS })); - await ensureAccountContractsPublished(wallet, [defaultAccountAddress]); ({ contract: eventOnlyContract } = await EventOnlyContract.deploy(wallet).send({ from: defaultAccountAddress })); }); diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index e722ce0ec332..604352e3ca00 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -27,9 +27,8 @@ import { MNEMONIC, getPaddedMaxFeesPerGas } from '../fixtures/fixtures.js'; import { type EndToEndContext, type SetupOptions, - deployAccounts, + createFundedAccounts, ensureAuthRegistryPublished, - publicDeployAccounts, setup, teardown, } from '../fixtures/setup.js'; @@ -114,7 +113,6 @@ export class FeesTest { ...this.setupOptions, ...opts, fundSponsoredFPC: true, - skipAccountDeployment: true, l1ContractsArgs: { ...this.setupOptions }, txPublicSetupAllowListExtend: [...(this.setupOptions.txPublicSetupAllowListExtend ?? []), ...tokenAllowList], }); @@ -179,7 +177,6 @@ export class FeesTest { public async applyBaseSetup() { await this.applyInitialAccounts(); await this.applyEnsureAuthRegistryPublished(); - await this.applyPublicDeployAccounts(); await this.applySetupFeeJuice(); await this.applyDeployBananaToken(); } @@ -192,7 +189,7 @@ export class FeesTest { async applyInitialAccounts() { this.logger.info('Applying initial accounts setup'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( this.numberOfAccounts, this.logger, )({ @@ -207,7 +204,7 @@ export class FeesTest { maxFeesPerGas: await getPaddedMaxFeesPerGas(this.aztecNode), }); this.cheatCodes = this.context.cheatCodes; - this.accounts = deployedAccounts.map(a => a.address); + this.accounts = accounts.map(a => a.address); this.accounts.forEach((a, i) => this.logger.verbose(`Account ${i} address: ${a}`)); [this.aliceAddress, this.bobAddress, this.sequencerAddress] = this.accounts.slice(0, 3); @@ -218,11 +215,6 @@ export class FeesTest { this.feeJuiceContract = FeeJuiceContract.at(canonicalFeeJuice.address, this.wallet); } - async applyPublicDeployAccounts() { - this.logger.info('Applying public deploy accounts setup'); - await publicDeployAccounts(this.wallet, this.accounts); - } - async applySetupFeeJuice() { this.logger.info('Applying fee juice setup'); diff --git a/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts b/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts index 1543890e7c59..d88c17e9ebbd 100644 --- a/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts +++ b/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts @@ -16,7 +16,7 @@ describe('e2e_genesis_timestamp', () => { // anchor lags behind proposed blocks. Under AUTOMINE_E2E_OPTS the AnvilTestWatcher is disabled // and the AutomineSequencer never marks blocks as proven on its own, so without a prover node // the proven tip stays at genesis for the duration of the test. - context = await setup(0, { ...AUTOMINE_E2E_OPTS, skipAccountDeployment: true }, { syncChainTip: 'proven' }); + context = await setup(0, { ...AUTOMINE_E2E_OPTS, advancePastGenesis: false }, { syncChainTip: 'proven' }); }); afterEach(() => context.teardown()); diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index b03736c4f1f5..a629781d0c83 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -15,7 +15,7 @@ import { afterAll, jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; import type { EndToEndContext } from './fixtures/setup.js'; import { mintTokensToPrivate } from './fixtures/token_utils.js'; -import { ensureAccountContractsPublished, ensureAuthRegistryPublished, setup } from './fixtures/utils.js'; +import { ensureAuthRegistryPublished, setup } from './fixtures/utils.js'; import { LendingAccount, LendingSimulator, TokenSimulator } from './simulators/index.js'; import type { TestWallet } from './test-wallet/test_wallet.js'; @@ -93,7 +93,6 @@ describe('e2e_lending_contract', () => { accounts: [defaultAccountAddress], } = ctx); ({ lendingContract, priceFeedContract, collateralAsset, stableCoin } = await deployContracts()); - await ensureAccountContractsPublished(wallet, [defaultAccountAddress]); await ensureAuthRegistryPublished(wallet, defaultAccountAddress); const rollup = new RollupContract( diff --git a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts index 5b662846434c..165fb83c3baf 100644 --- a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts +++ b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts @@ -77,7 +77,7 @@ describe('e2e_multi_validator_node', () => { ({ teardown, logger, wallet, initialFundedAccounts, aztecNode, config, deployL1ContractsValues, cheatCodes } = await setup( - 1, + 0, { ...PIPELINING_SETUP_OPTS, initialValidators, @@ -87,7 +87,6 @@ describe('e2e_multi_validator_node', () => { sequencerPollingIntervalMS: 200, worldStateBlockCheckIntervalMS: 200, blockCheckIntervalMS: 200, - skipAccountDeployment: true, }, { syncChainTip: 'checkpointed' }, )); diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts index d68dcc808b16..bb4e60fef6ad 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts @@ -8,8 +8,7 @@ import { ParentContract } from '@aztec/noir-test-contracts.js/Parent'; import { type EndToEndContext, type SetupOptions, - deployAccounts, - publicDeployAccounts, + createFundedAccounts, setup, teardown as teardownSubsystems, } from '../fixtures/setup.js'; @@ -36,7 +35,7 @@ export class NestedContractTest { */ async applyBaseSetup() { this.logger.info('Deploying accounts'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( this.numberOfAccounts, this.logger, )({ @@ -44,11 +43,8 @@ export class NestedContractTest { initialFundedAccounts: this.context.initialFundedAccounts, }); this.wallet = this.context.wallet; - [{ address: this.defaultAccountAddress }] = deployedAccounts; + [{ address: this.defaultAccountAddress }] = accounts; this.aztecNode = this.context.aztecNodeService; - - this.logger.info('Public deploy accounts'); - await publicDeployAccounts(this.wallet, [this.defaultAccountAddress]); } async setup(opts: Partial = {}) { @@ -56,7 +52,6 @@ export class NestedContractTest { this.context = await setup(0, { ...opts, fundSponsoredFPC: true, - skipAccountDeployment: true, }); await this.applyBaseSetup(); } 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 3ed31f0279e9..5a043873ced3 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,5 @@ import { type InitialAccountData, getInitialTestAccountsData } from '@aztec/accounts/testing'; import type { AztecNodeService } from '@aztec/aztec-node'; -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'; @@ -291,11 +290,10 @@ describe('e2e_p2p_add_rollup', () => { { ...getPXEConfig(), proverEnabled: false, syncChainTip: 'checkpointed' }, { loggerActorLabel: 'pxe-bridge' }, ); - const aliceAccountManager = await wallet.createSchnorrAccount(aliceAccount.secret, aliceAccount.salt); - const aliceDeploymethod = await aliceAccountManager.getDeployMethod(); - await aliceDeploymethod.send({ - from: NO_FROM, - }); + const aliceAccountManager = await wallet.createSchnorrInitializerlessAccount( + aliceAccount.secret, + aliceAccount.salt, + ); const aliceAddress = aliceAccountManager.address; diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index e912de390a05..2dbd1044e6e2 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -376,7 +376,6 @@ export class P2PNetworkTest { { ...this.setupOptions, fundSponsoredFPC: true, - skipAccountDeployment: true, skipInitialSequencer: true, initialFundedAccounts: [...regularAccounts, this.hardcodedAccountData], slasherEnabled: this.setupOptions.slasherEnabled ?? this.deployL1ContractsArgs.slasherEnabled ?? false, 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 eb95417f59d0..5155ad52b44d 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,5 +1,4 @@ import { getInitialTestAccountsData } from '@aztec/accounts/testing'; -import { NO_FROM } from '@aztec/aztec.js/account'; import type { AztecNode } from '@aztec/aztec.js/node'; import type { TxReceipt } from '@aztec/aztec.js/tx'; import { Bot, type BotConfig, BotStore, getBotDefaultConfig } from '@aztec/bot'; @@ -51,13 +50,7 @@ describe('e2e_sequencer_config', () => { minFeePadding: PIPELINED_FEE_PADDING, }; 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: NO_FROM }); + await wallet.createSchnorrInitializerlessAccount(botAccount.secret, botAccount.salt, botAccount.signingKey); bot = await Bot.create(config, wallet, aztecNode, undefined, new BotStore(await openTmpStore('bot'))); }); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts index b9db38db8573..a6d7eb25a024 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts @@ -10,9 +10,8 @@ import { jest } from '@jest/globals'; import { type EndToEndContext, type SetupOptions, - deployAccounts, + createFundedAccounts, ensureAuthRegistryPublished, - publicDeployAccounts, setup, teardown, } from '../fixtures/setup.js'; @@ -72,7 +71,7 @@ export class TokenContractTest { jest.setTimeout(120_000); this.logger.info('Applying base setup - deploying 3 accounts'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( 3, this.logger, )({ @@ -82,12 +81,10 @@ export class TokenContractTest { this.node = this.context.aztecNodeService; this.wallet = this.context.wallet; - [this.adminAddress, this.account1Address, this.account2Address] = deployedAccounts.map(acc => acc.address); + [this.adminAddress, this.account1Address, this.account2Address] = accounts.map(acc => acc.address); this.logger.info('Applying base setup - deploying token contract'); await ensureAuthRegistryPublished(this.wallet, this.adminAddress); - this.logger.verbose(`Public deploy accounts...`); - await publicDeployAccounts(this.wallet, [this.adminAddress, this.account1Address]); this.logger.verbose(`Deploying TokenContract...`); ({ contract: this.asset } = await TokenContract.deploy( @@ -129,7 +126,6 @@ export class TokenContractTest { ...opts, metricsPort: this.metricsPort, fundSponsoredFPC: true, - skipAccountDeployment: true, }); if (this.shouldApplyBaseSetup) { diff --git a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts index 3e345cd6d98b..2b68efb95656 100644 --- a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts @@ -25,10 +25,9 @@ import { getBBConfig } from './get_bb_config.js'; import { type EndToEndContext, type SetupOptions, - deployAccounts, + createFundedAccounts, getPrivateKeyFromIndex, getSponsoredFPCAddress, - publicDeployAccounts, setup, setupPXEAndGetWallet, teardown, @@ -54,7 +53,7 @@ export class FullProverTest { wallet!: TestWallet; provenWallet!: TestWallet; accounts: AztecAddress[] = []; - deployedAccounts!: InitialAccountData[]; + fundedAccounts!: InitialAccountData[]; fakeProofsAsset!: TokenContract; fakeProofsAssetInstance!: ContractInstanceWithAddress; tokenSim!: TokenSimulator; @@ -90,20 +89,17 @@ export class FullProverTest { */ private async applyBaseSetup() { this.logger.info('Applying base setup: deploying accounts'); - const { deployedAccounts } = await deployAccounts( + const { accounts } = await createFundedAccounts( 2, this.logger, )({ wallet: this.context.wallet, initialFundedAccounts: this.context.initialFundedAccounts, }); - this.deployedAccounts = deployedAccounts; - this.accounts = deployedAccounts.map(a => a.address); + this.fundedAccounts = accounts; + this.accounts = accounts.map(a => a.address); this.wallet = this.context.wallet; - this.logger.info('Applying base setup: publicly deploying accounts'); - await publicDeployAccounts(this.wallet, this.accounts.slice(0, 2)); - this.logger.info('Applying base setup: deploying token contract'); const { contract: asset, instance } = await TokenContract.deploy( this.wallet, @@ -132,7 +128,6 @@ export class FullProverTest { startProverNode: true, coinbase: this.coinbase, fundSponsoredFPC: true, - skipAccountDeployment: true, l1ContractsArgs: { realVerifier: this.realProofs }, }); @@ -197,8 +192,8 @@ export class FullProverTest { await provenWallet.registerContract(this.fakeProofsAssetInstance, TokenContract.artifact); for (let i = 0; i < 2; i++) { - await provenWallet.createSchnorrAccount(this.deployedAccounts[i].secret, this.deployedAccounts[i].salt); - await this.wallet.createSchnorrAccount(this.deployedAccounts[i].secret, this.deployedAccounts[i].salt); + await provenWallet.createSchnorrAccount(this.fundedAccounts[i].secret, this.fundedAccounts[i].salt); + await this.wallet.createSchnorrAccount(this.fundedAccounts[i].secret, this.fundedAccounts[i].salt); } const asset = TokenContract.at(this.fakeProofsAsset.address, provenWallet); diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index 94b1934ed123..bfb746f5779e 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -1,17 +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, - type ContractFunctionInteraction, - type ContractMethod, - type DeployOptions, - type InteractionWaitOptions, - getContractClassFromArtifact, - waitForProven, -} from '@aztec/aztec.js/contracts'; +import { type ContractMethod } from '@aztec/aztec.js/contracts'; import { publishContractClass, publishInstance } from '@aztec/aztec.js/deployment'; import { Fr } from '@aztec/aztec.js/fields'; import { type Logger, createLogger } from '@aztec/aztec.js/log'; @@ -66,7 +56,7 @@ import { initTelemetryClient, } from '@aztec/telemetry-client'; import { BenchmarkTelemetryClient } from '@aztec/telemetry-client/bench'; -import { deployFundedSchnorrAccounts } from '@aztec/wallets/testing'; +import { createFundedInitializerlessAccounts } from '@aztec/wallets/testing'; import { getGenesisValues } from '@aztec/world-state/testing'; import fs from 'fs/promises'; @@ -207,8 +197,11 @@ export type SetupOptions = { zkPassportArgs?: ZKPassportArgs; /** Whether to fund the sponsored FPC in genesis (defaults to false). */ fundSponsoredFPC?: boolean; - /** Whether to skip deploying accounts during setup (legacy behavior for tests using deployAccounts helper). */ - skipAccountDeployment?: boolean; + /** + * Whether to advance the chain past genesis by mining an empty block during setup (defaults to true). + * Set to false for tests that must observe the chain at genesis (block 0). + */ + advancePastGenesis?: boolean; /** L1 contracts deployment arguments. */ l1ContractsArgs?: Partial; /** Wallet minimum fee padding multiplier */ @@ -539,24 +532,14 @@ export async function setup( // Transactions built against the genesis state must be included in block 1, otherwise they are dropped. // To avoid test failures from dropped transactions, we ensure progression beyond genesis before proceeding. - // For account deployments, we set minTxsPerBlock=1 and deploy accounts sequentially for guaranteed success. - // If no accounts need deployment, we await an empty block to confirm network progression. const originalMinTxsPerBlock = config.minTxsPerBlock; if (originalMinTxsPerBlock === undefined) { throw new Error('minTxsPerBlock is undefined in e2e test setup'); } - // Whether we're deploying accounts (and therefore need reliable block inclusion past genesis) - const shouldDeployAccounts = numberOfAccounts > 0 && !opts.skipAccountDeployment; - // Only set minTxsPerBlock=0 if we need an empty block (no accounts at all, not skipped deployment) - const needsEmptyBlock = numberOfAccounts === 0 && !opts.skipAccountDeployment; - // Pipelining is always on: the proposer builds during slot N-1 for slot N. A tx submitted at - // slot N start arrives after that build, so forcing minTxsPerBlock=1 would stall the chain on - // alternating slots -- hence empty checkpoints are allowed (minTxsPerBlock=0) for account - // deployment. Automine is unaffected: its runBuild clamps mempool builds to - // Math.max(minTxsPerBlock ?? 1, 1) and still requires minValidTxs: 1. - const accountsDeployMinTxs = 0; - config.minTxsPerBlock = shouldDeployAccounts ? accountsDeployMinTxs : needsEmptyBlock ? 0 : originalMinTxsPerBlock; + // Allow an empty checkpoint so the empty block can be built; leave untouched when not advancing. + const advancePastGenesis = (opts.advancePastGenesis ?? true) && !opts.skipInitialSequencer; + config.minTxsPerBlock = advancePastGenesis ? 0 : originalMinTxsPerBlock; config.p2pEnabled = opts.mockGossipSubNetwork || config.p2pEnabled; config.p2pIp = opts.p2pIp ?? config.p2pIp ?? '127.0.0.1'; @@ -651,18 +634,19 @@ export async function setup( let accounts: AztecAddress[] = []; - if (opts.skipInitialSequencer) { - logger.info('Sequencer not started on initial node, skipping block progression'); - } else if (shouldDeployAccounts) { - logger.info( - `${numberOfAccounts} accounts are being deployed. Reliably progressing past genesis by waiting for the accounts to be deployed`, - ); + // Account creation is a PXE-side operation (registration + a simulated store call) with no on-chain tx, + // so it is independent of the sequencer and runs even when the initial sequencer is not started. + if (numberOfAccounts > 0) { + logger.info(`Creating ${numberOfAccounts} initializerless test accounts`); const accountsData = initialFundedAccounts.slice(0, numberOfAccounts); - const accountManagers = await deployFundedSchnorrAccounts(wallet, accountsData); + const accountManagers = await createFundedInitializerlessAccounts(wallet, accountsData); accounts = accountManagers.map(accountManager => accountManager.address); - } else if (needsEmptyBlock) { - logger.info('No accounts are being deployed, waiting for an empty block 1 to be mined'); - // AutomineSequencer only builds on tx arrival; explicitly request an empty block. + } + + // Advancing past genesis needs a running sequencer to build the empty block; advancePastGenesis is + // already false when skipInitialSequencer is set. + if (advancePastGenesis) { + logger.info('Mining an empty block to progress past genesis'); const automine = aztecNodeService.getAutomineSequencer(); if (automine) { await automine.buildEmptyBlock(); @@ -670,8 +654,9 @@ export async function setup( while ((await aztecNodeService.getBlockNumber()) === 0) { await sleep(2000); } + } else if (opts.skipInitialSequencer) { + logger.info('Sequencer not started on initial node, skipping block progression'); } - // If skipAccountDeployment is true, we don't deploy or wait - tests will handle account deployment later // Now we restore the original minTxsPerBlock setting if we changed it. if (sequencerClient && config.minTxsPerBlock !== originalMinTxsPerBlock) { @@ -949,99 +934,24 @@ export async function ensureHandshakeRegistryPublished(wallet: Wallet, from: Azt } /** - * Registers the contract class used for test accounts and publicly deploys the instances requested. - * Use this when you need to make a public call to an account contract, such as for requesting a public authwit. - */ -export async function ensureAccountContractsPublished(wallet: Wallet, accountsToDeploy: AztecAddress[]) { - const accountsAndAddresses = await Promise.all( - accountsToDeploy.map(async address => { - return { - address, - deployed: (await wallet.getContractMetadata(address)).isContractPublished, - }; - }), - ); - const instances = ( - await Promise.all( - accountsAndAddresses - .filter(({ deployed }) => !deployed) - .map(({ address }) => wallet.getContractMetadata(address)), - ) - ).map(contractMetadata => contractMetadata.instance); - const contractClass = await getContractClassFromArtifact(SchnorrAccountContractArtifact); - if (!(await wallet.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered) { - await (await publishContractClass(wallet, SchnorrAccountContractArtifact)).send({ from: accountsToDeploy[0] }); - } - const requests = instances.map(instance => publishInstance(wallet, instance!)); - const batch = new BatchCall(wallet, requests); - await batch.send({ from: accountsToDeploy[0] }); -} - -/** - * Helper function to deploy accounts. - * Returns deployed account data that can be used by tests. + * Helper function to create the initial (genesis-funded) test accounts as initializerless accounts. + * Returns the account data that can be used by tests. Initializerless accounts have no deployment tx, so + * creating them only registers the instances in the wallet; they are funded via genesis at their addresses. */ -export const deployAccounts = - (numberOfAccounts: number, logger: Logger, deployOptions?: Partial>) => +export const createFundedAccounts = + (numberOfAccounts: number, logger: Logger) => async ({ wallet, initialFundedAccounts }: { wallet: TestWallet; initialFundedAccounts: InitialAccountData[] }) => { if (initialFundedAccounts.length < numberOfAccounts) { - throw new Error(`Cannot deploy more than ${initialFundedAccounts.length} initial accounts.`); + throw new Error(`Cannot create more than ${initialFundedAccounts.length} initial accounts.`); } - logger.verbose('Deploying accounts funded with fee juice...'); - const deployedAccounts = initialFundedAccounts.slice(0, numberOfAccounts); - // Serial due to https://github.com/AztecProtocol/aztec-packages/issues/12045 - for (let i = 0; i < deployedAccounts.length; i++) { - const accountManager = await wallet.createSchnorrAccount( - deployedAccounts[i].secret, - deployedAccounts[i].salt, - deployedAccounts[i].signingKey, - ); - const deployMethod = await accountManager.getDeployMethod(); - await deployMethod.send({ - from: NO_FROM, - skipClassPublication: i !== 0, // Publish the contract class at most once. - ...deployOptions, - }); - } + logger.verbose('Creating initializerless accounts funded with fee juice...'); + const accounts = initialFundedAccounts.slice(0, numberOfAccounts); + await createFundedInitializerlessAccounts(wallet, accounts); - return { deployedAccounts }; + return { accounts }; }; -/** - * Registers the contract class used for test accounts and publicly deploys the instances requested. - * Use this when you need to make a public call to an account contract, such as for requesting a public authwit. - */ -export async function publicDeployAccounts( - wallet: Wallet, - accountsToDeploy: AztecAddress[], - waitUntilProven = false, - node?: AztecNode, -) { - const instances = (await Promise.all(accountsToDeploy.map(account => wallet.getContractMetadata(account)))).map( - metadata => metadata.instance, - ); - - const contractClass = await getContractClassFromArtifact(SchnorrAccountContractArtifact); - const alreadyRegistered = (await wallet.getContractClassMetadata(contractClass.id)).isContractClassPubliclyRegistered; - - const calls: ContractFunctionInteraction[] = await Promise.all([ - ...(!alreadyRegistered ? [publishContractClass(wallet, SchnorrAccountContractArtifact)] : []), - ...instances.map(instance => publishInstance(wallet, instance!)), - ]); - - const batch = new BatchCall(wallet, calls); - - const { receipt: txReceipt } = await batch.send({ from: accountsToDeploy[0] }); - if (waitUntilProven) { - if (!node) { - throw new Error('Need to provide an AztecNode to wait for proven.'); - } else { - await waitForProven(node, txReceipt); - } - } -} - /** * Destroys the current context. */ diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index a5f1907e229b..79c8a882a9d0 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -7,8 +7,7 @@ export { type EndToEndContext, type SetupOptions, createAndSyncProverNode, - deployAccounts, - ensureAccountContractsPublished, + createFundedAccounts, ensureAuthRegistryPublished, ensurePublicChecksPublished, expectMapping, @@ -18,7 +17,6 @@ export { getPrivateKeyFromIndex, getSponsoredFPCAddress, getSponsoredFPCInstance, - publicDeployAccounts, registerSponsoredFPC, setup, setupPXEAndGetWallet, diff --git a/yarn-project/end-to-end/src/forward-compatibility/wallet_service.ts b/yarn-project/end-to-end/src/forward-compatibility/wallet_service.ts index 674ef390f4db..4394bdb8c053 100644 --- a/yarn-project/end-to-end/src/forward-compatibility/wallet_service.ts +++ b/yarn-project/end-to-end/src/forward-compatibility/wallet_service.ts @@ -58,7 +58,9 @@ async function main() { // incompatible with Node.js import attribute enforcement. const testAccountsData = await getInitialTestAccountsData(); const accounts = await Promise.all( - testAccountsData.map(({ secret, salt, signingKey }) => wallet.createSchnorrAccount(secret, salt, signingKey)), + testAccountsData.map(({ secret, salt, signingKey }) => + wallet.createSchnorrInitializerlessAccount(secret, salt, signingKey), + ), ); // Register and deploy the 4th account. diff --git a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts index 1969c871b7c1..086018c7b0a6 100644 --- a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts +++ b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts @@ -21,11 +21,7 @@ import { computeL2ToL1MessageHash } from '@aztec/stdlib/hash'; import { jest } from '@jest/globals'; import { type GetContractReturnType, getContract, parseEther, toFunctionSelector } from 'viem'; -import { - type EndToEndContext, - ensureAccountContractsPublished, - ensureAuthRegistryPublished, -} from '../fixtures/utils.js'; +import { type EndToEndContext, ensureAuthRegistryPublished } from '../fixtures/utils.js'; import type { TestWallet } from '../test-wallet/test_wallet.js'; import { CrossChainTestHarness } from './cross_chain_test_harness.js'; @@ -101,7 +97,6 @@ export const uniswapL1L2TestSuite = ( version = Number(await rollup.getVersion()); ownerEthAddress = EthAddress.fromString((await l1Client.getAddresses())[0]); - await ensureAccountContractsPublished(wallet, [ownerAddress, sponsorAddress]); await ensureAuthRegistryPublished(wallet, ownerAddress); logger.info('Deploying DAI Portal, initializing and deploying l2 contract...'); 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 bb0662ad9640..8c22bf52aad6 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,6 +1,6 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/ecdsa/stub'; -import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; +import { SchnorrAccountContract, SchnorrInitializerlessAccountContract } from '@aztec/accounts/schnorr'; import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/schnorr/stub'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import type { CompleteAddress } from '@aztec/aztec.js/addresses'; @@ -13,11 +13,13 @@ import { isContractFunctionInteractionCallIntent, lookupValidity, } from '@aztec/aztec.js/authorization'; +import { ContractFunctionInteraction } from '@aztec/aztec.js/contracts'; import type { AztecNode } from '@aztec/aztec.js/node'; import { AccountManager, type SendOptions } from '@aztec/aztec.js/wallet'; import { TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; import { DefaultEntrypoint } from '@aztec/entrypoints/default'; +import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fq, Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { NotesFilter } from '@aztec/pxe/client/lazy'; @@ -96,6 +98,16 @@ export class TestWallet extends BaseWallet { return this.createAccount({ secret, salt, type: 'schnorr', contract: new SchnorrAccountContract(signingKey) }); } + createSchnorrInitializerlessAccount(secret: Fr, salt: Fr, signingKey?: Fq): Promise { + signingKey = signingKey ?? deriveSigningKey(secret); + return this.createAccount({ + secret, + salt, + type: 'schnorr_initializerless', + contract: new SchnorrInitializerlessAccountContract(signingKey), + }); + } + createECDSARAccount(secret: Fr, salt: Fr, signingKey: Buffer): Promise { return this.createAccount({ secret, @@ -131,6 +143,8 @@ export class TestWallet extends BaseWallet { await this.pxe.registerContractClass(StubEcdsaAccountContractArtifact); this.stubClassIds.set('schnorr', schnorrClassId); + // Initializerless accounts share the schnorr stub class for kernelless simulation. + this.stubClassIds.set('schnorr_initializerless', schnorrClassId); this.stubClassIds.set('ecdsasecp256k1', ecdsaClassId); this.stubClassIds.set('ecdsasecp256r1', ecdsaClassId); } @@ -178,7 +192,8 @@ export class TestWallet extends BaseWallet { } private getStubAccountFor(address: AztecAddress, completeAddress: CompleteAddress) { - return this.getTypeFor(address) === 'schnorr' + const type = this.getTypeFor(address); + return type === 'schnorr' || type === 'schnorr_initializerless' ? createStubSchnorrAccount(completeAddress) : createStubEcdsaAccount(completeAddress); } @@ -221,7 +236,14 @@ export class TestWallet extends BaseWallet { const type = accountData?.type ?? 'schnorr'; const contract = accountData?.contract ?? new SchnorrAccountContract(GrumpkinScalar.random()); - const accountManager = await AccountManager.create(this, secret, contract, { salt }); + // Initializerless accounts have no deployment tx: the address commits to the public keys (immutables) + // and the constructor's storage writes are materialized locally via a simulated "store" call below. + // Mirrors EmbeddedWallet.createAccountInternal. + const isInitializerless = type === 'schnorr_initializerless'; + const init = isInitializerless ? await contract.getInitializationFunctionAndArgs() : undefined; + const immutablesHash = init ? await poseidon2Hash(init.constructorArgs) : undefined; + + const accountManager = await AccountManager.create(this, secret, contract, { salt, immutablesHash }); const instance = accountManager.getInstance(); const artifact = await contract.getContractArtifact(); @@ -231,6 +253,15 @@ export class TestWallet extends BaseWallet { const address = accountManager.address.toString(); this.accounts.set(address, { account: await accountManager.getAccount(), type }); + if (init) { + const constructorAbi = artifact.functions.find(f => f.name === init.constructorName); + if (!constructorAbi) { + throw new Error('Could not create SchnorrInitializerlessAccount: constructor ABI not found'); + } + const storeCall = new ContractFunctionInteraction(this, instance.address, constructorAbi, init.constructorArgs); + await storeCall.simulate({ from: instance.address }); + } + return accountManager; } diff --git a/yarn-project/wallets/src/testing.ts b/yarn-project/wallets/src/testing.ts index 3f391caa9fe1..791ed6028944 100644 --- a/yarn-project/wallets/src/testing.ts +++ b/yarn-project/wallets/src/testing.ts @@ -1,32 +1,25 @@ 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'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; interface WalletWithSchnorrAccounts { - createSchnorrAccount(secret: Fr, salt: Fr, signingKey?: Fq, alias?: string): Promise; + createSchnorrInitializerlessAccount(secret: Fr, salt: Fr, signingKey?: Fq, alias?: string): Promise; } -export async function deployFundedSchnorrAccounts( +/** + * Creates the given (genesis-funded) test accounts as initializerless schnorr accounts. Initializerless + * accounts need no deployment tx — creating one registers the instance and materializes its immutable keys + * locally — so the accounts are usable as soon as they are created, funded via genesis at their addresses. + */ +export async function createFundedInitializerlessAccounts( wallet: WalletWithSchnorrAccounts, accountsData: InitialAccountData[], - waitOptions?: WaitOpts, ) { const accountManagers = []; - // Serial due to https://github.com/AztecProtocol/aztec-packages/issues/12045 - for (let i = 0; i < accountsData.length; i++) { - const { secret, salt, signingKey } = accountsData[i]; - const accountManager = await wallet.createSchnorrAccount(secret, salt, signingKey); - const deployMethod = await accountManager.getDeployMethod(); - await deployMethod.send({ - from: NO_FROM, - skipClassPublication: i !== 0, - wait: waitOptions, - }); - accountManagers.push(accountManager); + for (const { secret, salt, signingKey } of accountsData) { + accountManagers.push(await wallet.createSchnorrInitializerlessAccount(secret, salt, signingKey)); } return accountManagers; } @@ -37,7 +30,8 @@ export async function registerInitialLocalNetworkAccountsInWallet( const testAccounts = await getInitialTestAccountsData(); return Promise.all( testAccounts.map(async account => { - return (await wallet.createSchnorrAccount(account.secret, account.salt, account.signingKey)).address; + return (await wallet.createSchnorrInitializerlessAccount(account.secret, account.salt, account.signingKey)) + .address; }), ); } From 6076fd1d2cf06c02b26bc2f486c051288747f953 Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Wed, 10 Jun 2026 13:53:40 +0200 Subject: [PATCH 09/23] more e2e migration --- .../accounts/src/testing/configuration.ts | 11 ++++ yarn-project/accounts/src/testing/index.ts | 23 ++++++- yarn-project/accounts/src/testing/lazy.ts | 22 ++++++- .../client_flows/client_flows_benchmark.ts | 20 +----- .../src/composed/e2e_persistence.test.ts | 42 +++++++------ .../src/composed/ha/e2e_ha_full.test.ts | 10 +-- .../end-to-end/src/e2e_2_pxes.test.ts | 18 +++--- .../src/e2e_account_contracts.test.ts | 7 +-- .../blacklist_token_contract_test.ts | 16 +---- .../end-to-end/src/e2e_block_building.test.ts | 11 +++- yarn-project/end-to-end/src/e2e_bot.test.ts | 2 +- .../end-to-end/src/e2e_card_game.test.ts | 18 ++++-- .../src/e2e_contract_updates.test.ts | 39 ++++++------ .../cross_chain_messaging_test.ts | 14 +---- .../src/e2e_deploy_contract/deploy_test.ts | 18 +----- .../end-to-end/src/e2e_fees/fees_test.ts | 13 +--- .../src/e2e_genesis_timestamp.test.ts | 18 ++++-- yarn-project/end-to-end/src/e2e_keys.test.ts | 22 +++---- .../e2e_multi_validator_node.test.ts | 10 +-- .../e2e_multiple_accounts_1_enc_key.test.ts | 23 ++++--- .../nested_contract_test.ts | 32 ++-------- .../end-to-end/src/e2e_p2p/add_rollup.test.ts | 2 +- .../end-to-end/src/e2e_p2p/p2p_network.ts | 9 ++- .../e2e_public_testnet_transfer.test.ts | 3 +- .../src/e2e_sequencer_config.test.ts | 2 +- .../end-to-end/src/e2e_synching.test.ts | 20 +++--- .../e2e_token_contract/token_contract_test.ts | 14 +---- .../src/fixtures/e2e_prover_test.ts | 32 +++++----- yarn-project/end-to-end/src/fixtures/setup.ts | 61 ++++++------------- yarn-project/end-to-end/src/fixtures/utils.ts | 1 - 30 files changed, 249 insertions(+), 284 deletions(-) diff --git a/yarn-project/accounts/src/testing/configuration.ts b/yarn-project/accounts/src/testing/configuration.ts index 731332bccec7..6d407e9b6809 100644 --- a/yarn-project/accounts/src/testing/configuration.ts +++ b/yarn-project/accounts/src/testing/configuration.ts @@ -17,6 +17,12 @@ export const INITIAL_TEST_SIGNING_KEYS = INITIAL_TEST_ENCRYPTION_KEYS; export const INITIAL_TEST_ACCOUNT_SALTS = [Fr.ZERO, Fr.ZERO, Fr.ZERO]; +/** + * The schnorr account contract variant a test account uses. Determines both how its address is derived and + * which account type is created when prefunding it. Defaults to `schnorr_initializerless` when omitted. + */ +export type InitialAccountType = 'schnorr' | 'schnorr_initializerless'; + /** * Data for generating an initial account. */ @@ -37,4 +43,9 @@ export interface InitialAccountData { * Address of the schnorr account contract. */ address: AztecAddress; + /** + * Account contract variant to create when prefunding this account. The address above is derived from it. + * Omitted for special account contracts (e.g. hardcoded-key test accounts) that callers create themselves. + */ + type?: InitialAccountType; } diff --git a/yarn-project/accounts/src/testing/index.ts b/yarn-project/accounts/src/testing/index.ts index 0804bb660493..d5aa46dc09c0 100644 --- a/yarn-project/accounts/src/testing/index.ts +++ b/yarn-project/accounts/src/testing/index.ts @@ -4,15 +4,18 @@ * @packageDocumentation */ import { Fr } from '@aztec/aztec.js/fields'; +import type { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { getSchnorrInitializerlessAccountContractAddress } from '../schnorr/initializerless/index.js'; +import { getSchnorrAccountContractAddress } from '../schnorr/private_immutable/index.js'; import { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_ENCRYPTION_KEYS, INITIAL_TEST_SECRET_KEYS, INITIAL_TEST_SIGNING_KEYS, type InitialAccountData, + type InitialAccountType, } from './configuration.js'; export { @@ -21,8 +24,16 @@ export { INITIAL_TEST_SECRET_KEYS, INITIAL_TEST_SIGNING_KEYS, type InitialAccountData, + type InitialAccountType, } from './configuration.js'; +/** Derives the account contract address for the given type, so it matches the account that gets created. */ +function getTestAccountAddress(type: InitialAccountType, secret: Fr, salt: Fr, signingKey?: GrumpkinScalar) { + return type === 'schnorr' + ? getSchnorrAccountContractAddress(secret, salt, signingKey) + : getSchnorrInitializerlessAccountContractAddress(secret, salt, signingKey); +} + /** * Gets the basic information for initial test accounts. */ @@ -32,6 +43,7 @@ export function getInitialTestAccountsData(): Promise { secret, signingKey: INITIAL_TEST_ENCRYPTION_KEYS[i], salt: INITIAL_TEST_ACCOUNT_SALTS[i], + type: 'schnorr_initializerless' as const, address: await getSchnorrInitializerlessAccountContractAddress( secret, INITIAL_TEST_ACCOUNT_SALTS[i], @@ -42,9 +54,13 @@ export function getInitialTestAccountsData(): Promise { } /** - * Generate a fixed amount of random schnorr account contract instance. + * Generate a fixed amount of random schnorr account contract instances of the given type (defaults to + * initializerless). The returned addresses are derived to match the type so they can be prefunded. */ -export async function generateSchnorrAccounts(numberOfAccounts: number): Promise> { +export async function generateSchnorrAccounts( + numberOfAccounts: number, + type: InitialAccountType = 'schnorr_initializerless', +): Promise { const secrets = Array.from({ length: numberOfAccounts }, () => Fr.random()); return await Promise.all( secrets.map(async secret => { @@ -53,7 +69,8 @@ export async function generateSchnorrAccounts(numberOfAccounts: number): Promise secret, signingKey: deriveSigningKey(secret), salt, - address: await getSchnorrInitializerlessAccountContractAddress(secret, salt), + type, + address: await getTestAccountAddress(type, secret, salt), }; }), ); diff --git a/yarn-project/accounts/src/testing/lazy.ts b/yarn-project/accounts/src/testing/lazy.ts index 8bba3f235d60..05a33357ccc5 100644 --- a/yarn-project/accounts/src/testing/lazy.ts +++ b/yarn-project/accounts/src/testing/lazy.ts @@ -4,19 +4,29 @@ * @packageDocumentation */ import { Fr } from '@aztec/aztec.js/fields'; +import type { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { getSchnorrInitializerlessAccountContractAddress } from '../schnorr/initializerless/lazy.js'; +import { getSchnorrAccountContractAddress } from '../schnorr/private_immutable/lazy.js'; import { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_ENCRYPTION_KEYS, INITIAL_TEST_SECRET_KEYS, INITIAL_TEST_SIGNING_KEYS, type InitialAccountData, + type InitialAccountType, } from './configuration.js'; export { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_SECRET_KEYS } from './configuration.js'; +/** Derives the account contract address for the given type, so it matches the account that gets created. */ +function getTestAccountAddress(type: InitialAccountType, secret: Fr, salt: Fr, signingKey?: GrumpkinScalar) { + return type === 'schnorr' + ? getSchnorrAccountContractAddress(secret, salt, signingKey) + : getSchnorrInitializerlessAccountContractAddress(secret, salt, signingKey); +} + /** * Gets the basic information for initial test accounts. */ @@ -26,6 +36,7 @@ export function getInitialTestAccountsData(): Promise { secret, signingKey: INITIAL_TEST_ENCRYPTION_KEYS[i], salt: INITIAL_TEST_ACCOUNT_SALTS[i], + type: 'schnorr_initializerless' as const, address: await getSchnorrInitializerlessAccountContractAddress( secret, INITIAL_TEST_ACCOUNT_SALTS[i], @@ -36,9 +47,13 @@ export function getInitialTestAccountsData(): Promise { } /** - * Generate a fixed amount of random schnorr account contract instance. + * Generate a fixed amount of random schnorr account contract instances of the given type (defaults to + * initializerless). The returned addresses are derived to match the type so they can be prefunded. */ -export async function generateSchnorrAccounts(numberOfAccounts: number): Promise { +export async function generateSchnorrAccounts( + numberOfAccounts: number, + type: InitialAccountType = 'schnorr_initializerless', +): Promise { const secrets = Array.from({ length: numberOfAccounts }, () => Fr.random()); return await Promise.all( secrets.map(async secret => { @@ -47,7 +62,8 @@ export async function generateSchnorrAccounts(numberOfAccounts: number): Promise secret, signingKey: deriveSigningKey(secret), salt, - address: await getSchnorrInitializerlessAccountContractAddress(secret, salt), + type, + address: await getTestAccountAddress(type, secret, salt), }; }), ); 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 0c325fc4e82a..3a282a6d8b64 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 @@ -29,13 +29,7 @@ import { GasSettings } from '@aztec/stdlib/gas'; import { deriveSigningKey } from '@aztec/stdlib/keys'; import { AUTOMINE_E2E_OPTS, MNEMONIC, getPaddedMaxFeesPerGas } from '../../fixtures/fixtures.js'; -import { - type EndToEndContext, - type SetupOptions, - createFundedAccounts, - setup, - teardown, -} from '../../fixtures/setup.js'; +import { type EndToEndContext, type SetupOptions, setup, teardown } from '../../fixtures/setup.js'; import { mintTokensToPrivate } from '../../fixtures/token_utils.js'; import { setupSponsoredFPC } from '../../fixtures/utils.js'; import { CrossChainTestHarness } from '../../shared/cross_chain_test_harness.js'; @@ -141,7 +135,7 @@ export class ClientFlowsBenchmark { this.logger.info('Setting up subsystems from fresh'); // Token allowlist entries are test-only: FPC-based fee payment with custom tokens won't work on mainnet alpha. const tokenAllowList = await getTokenAllowedSetupFunctions(); - this.context = await setup(0, { + this.context = await setup(2, { ...this.setupOptions, fundSponsoredFPC: true, l1ContractsArgs: this.setupOptions, @@ -208,15 +202,7 @@ export class ClientFlowsBenchmark { async applyInitialAccounts() { this.logger.info('Applying initial accounts setup'); - const { accounts } = await createFundedAccounts( - 2, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - - const [{ address: adminAddress }, { address: sequencerAddress }] = accounts; + const [adminAddress, sequencerAddress] = this.context.accounts; this.adminWallet = this.context.wallet; this.aztecNode = this.context.aztecNodeService; diff --git a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts index 0853af29b175..04c20b23ca60 100644 --- a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts @@ -1,4 +1,4 @@ -import type { InitialAccountData } from '@aztec/accounts/testing'; +import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; import type { ContractInstanceWithAddress } from '@aztec/aztec.js/contracts'; import { computeSecretHash } from '@aztec/aztec.js/crypto'; import type { AztecNode } from '@aztec/aztec.js/node'; @@ -46,7 +46,7 @@ describe('Aztec persistence', () => { let ownerAddress: AztecAddress; // Data of the funded accounts that can deploy themselves. - let initialFundedAccounts: InitialAccountData[]; + let additionallyFundedAccounts: InitialAccountData[]; // a directory where data will be persisted by components // passing this through to the Node or PXE will control whether they use persisted data or not @@ -62,16 +62,18 @@ describe('Aztec persistence', () => { dataDirectory = await mkdtemp(join(tmpdir(), 'aztec-node-')); const initialContext = await setup( - 1, - { ...PIPELINING_SETUP_OPTS, dataDirectory, numberOfInitialFundedAccounts: 3 }, + 0, + { ...PIPELINING_SETUP_OPTS, dataDirectory, additionallyFundedAccounts: await generateSchnorrAccounts(3) }, { dataDirectory }, ); aztecNode = initialContext.aztecNode; deployL1ContractsValues = initialContext.deployL1ContractsValues; - initialFundedAccounts = initialContext.initialFundedAccounts; + additionallyFundedAccounts = initialContext.additionallyFundedAccounts; wallet = initialContext.wallet; - owner = initialFundedAccounts[0]; + owner = additionallyFundedAccounts[0]; ownerAddress = owner.address; + // The owner is funded but not created by setup; create it (initializerless) so it can transact. + await wallet.createSchnorrInitializerlessAccount(owner.secret, owner.salt, owner.signingKey); const { contract, instance } = await TokenBlacklistContract.deploy(wallet, ownerAddress).send({ from: ownerAddress, @@ -121,7 +123,7 @@ describe('Aztec persistence', () => { () => setup( 0, - { ...PIPELINING_SETUP_OPTS, dataDirectory, deployL1ContractsValues, initialFundedAccounts }, + { ...PIPELINING_SETUP_OPTS, dataDirectory, deployL1ContractsValues, additionallyFundedAccounts }, { dataDirectory }, ), 60_000, @@ -129,7 +131,8 @@ describe('Aztec persistence', () => { [ // ie our PXE was restarted, data kept intact and now connects to a "new" Node. Initial synch will synch from scratch 'when starting a PXE with an existing database, connected to a Node with database synched from scratch', - () => setup(0, { ...PIPELINING_SETUP_OPTS, deployL1ContractsValues, initialFundedAccounts }, { dataDirectory }), + () => + setup(0, { ...PIPELINING_SETUP_OPTS, deployL1ContractsValues, additionallyFundedAccounts }, { dataDirectory }), 120_000, ], ])('%s', (_, contextSetup, timeout) => { @@ -137,7 +140,7 @@ describe('Aztec persistence', () => { beforeEach(async () => { context = await contextSetup(); - await context.wallet.createSchnorrAccount(owner.secret, owner.salt, owner.signingKey); + await context.wallet.createSchnorrInitializerlessAccount(owner.secret, owner.salt, owner.signingKey); contract = TokenBlacklistContract.at(contractAddress, wallet); }, timeout); @@ -193,8 +196,8 @@ describe('Aztec persistence', () => { }); it('allows spending of private notes', async () => { - const account = initialFundedAccounts[1]; // Not the owner account. - const otherAccount = await context.wallet.createSchnorrAccount(account.secret, account.salt); + const account = additionallyFundedAccounts[1]; // Not the owner account. + const otherAccount = await context.wallet.createSchnorrInitializerlessAccount(account.secret, account.salt); const otherAddress = otherAccount.address; const { result: initialOwnerBalance } = await contract.methods @@ -217,13 +220,14 @@ describe('Aztec persistence', () => { [ // ie. I'm setting up a new full node, sync from scratch and restore wallets/notes 'when starting the Node and PXE with empty databases', - () => setup(0, { ...PIPELINING_SETUP_OPTS, deployL1ContractsValues, initialFundedAccounts }, {}), + () => setup(0, { ...PIPELINING_SETUP_OPTS, deployL1ContractsValues, additionallyFundedAccounts }, {}), 120_000, ], [ // ie. I'm setting up a new PXE, restore wallets/notes from a Node 'when starting a PXE with an empty database connected to a Node with an existing database', - () => setup(0, { ...PIPELINING_SETUP_OPTS, dataDirectory, deployL1ContractsValues, initialFundedAccounts }, {}), + () => + setup(0, { ...PIPELINING_SETUP_OPTS, dataDirectory, deployL1ContractsValues, additionallyFundedAccounts }, {}), 120_000, ], ])('%s', (_, contextSetup, timeout) => { @@ -269,8 +273,8 @@ describe('Aztec persistence', () => { it('pxe restores notes after registering the owner', async () => { await context.wallet.registerContract(contractInstance, TokenBlacklistContract.artifact); - const account = initialFundedAccounts[0]; - await context.wallet.createSchnorrAccount(account.secret, account.salt); + const account = additionallyFundedAccounts[0]; + await context.wallet.createSchnorrInitializerlessAccount(account.secret, account.salt); const contract = TokenBlacklistContract.at(contractAddress, context.wallet); // check that notes total more than 0 so that this test isn't dependent on run order @@ -299,8 +303,8 @@ describe('Aztec persistence', () => { await temporaryContext.wallet.registerContract(contractInstance, TokenBlacklistContract.artifact); - const account = initialFundedAccounts[0]; - await context.wallet.createSchnorrAccount(account.secret, account.salt); + const account = additionallyFundedAccounts[0]; + await context.wallet.createSchnorrInitializerlessAccount(account.secret, account.salt); const contract = TokenBlacklistContract.at(contractAddress, context.wallet); @@ -324,8 +328,8 @@ describe('Aztec persistence', () => { beforeEach(async () => { context = await setup(0, { ...PIPELINING_SETUP_OPTS, dataDirectory, deployL1ContractsValues }, { dataDirectory }); - const account = initialFundedAccounts[0]; - await context.wallet.createSchnorrAccount(account.secret, account.salt); + const account = additionallyFundedAccounts[0]; + await context.wallet.createSchnorrInitializerlessAccount(account.secret, account.salt); contract = TokenBlacklistContract.at(contractAddress, context.wallet); }, 120_000); 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 4c1ab31367bf..9d62dd2d7c2e 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 @@ -5,7 +5,7 @@ * and Web3Signer for remote signing. Verifies that blocks are produced, * attestations are signed, and no double-signing occurs. */ -import type { InitialAccountData } from '@aztec/accounts/testing'; +import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; import { type AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; import { NO_FROM } from '@aztec/aztec.js/account'; import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; @@ -74,7 +74,7 @@ describe('HA Full Setup', () => { let aztecNode: AztecNode; let config: AztecNodeConfig; let teardown: () => Promise; - let initialFundedAccounts: InitialAccountData[]; + let additionallyFundedAccounts: InitialAccountData[]; let dateProvider: TestDateProvider; let genesis: GenesisData | undefined; @@ -152,7 +152,7 @@ describe('HA Full Setup', () => { wallet, aztecNode, config, - initialFundedAccounts, + additionallyFundedAccounts, dateProvider, deployL1ContractsValues, genesis, @@ -167,6 +167,8 @@ describe('HA Full Setup', () => { sequencerPollingIntervalMS: 200, worldStateBlockCheckIntervalMS: 200, blockCheckIntervalMS: 200, + // We deploy this account ourselves later, so fund it as a regular (deployable) schnorr account. + additionallyFundedAccounts: await generateSchnorrAccounts(1, 'schnorr'), startProverNode: true, // Disable validation on this node disableValidator: true, @@ -290,7 +292,7 @@ describe('HA Full Setup', () => { // Now deploy the account - blocks can be built by the HA nodes logger.info('Deploying test account now that validators are running'); - const accountData = initialFundedAccounts[0]; + const accountData = additionallyFundedAccounts[0]; const accountManager = await wallet.createSchnorrAccount( accountData.secret, accountData.salt, 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 22274e82c59f..2c88438732db 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,4 @@ -import type { InitialAccountData } from '@aztec/accounts/testing'; +import { type InitialAccountData, generateSchnorrAccounts } 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'; @@ -24,7 +24,7 @@ describe('e2e_2_pxes', () => { let walletB: TestWallet; let accountAAddress: AztecAddress; let accountBAddress: AztecAddress; - let initialFundedAccounts: InitialAccountData[]; + let additionallyFundedAccounts: InitialAccountData[]; let logger: Logger; let teardownA: () => Promise; let teardownB: () => Promise; @@ -48,18 +48,22 @@ describe('e2e_2_pxes', () => { beforeEach(async () => { ({ aztecNode, - initialFundedAccounts, + additionallyFundedAccounts, wallet: walletA, accounts: [accountAAddress], logger, teardown: teardownA, - } = await setup(1, { ...AUTOMINE_E2E_OPTS, numberOfInitialFundedAccounts: 3 })); + // accountA is the default initializerless account; accountB/C are created+deployed from these. + } = await setup(1, { + ...AUTOMINE_E2E_OPTS, + additionallyFundedAccounts: await generateSchnorrAccounts(3, 'schnorr'), + })); ({ wallet: walletB, address: accountBAddress, teardown: teardownB, - } = await setupSecondaryPXE(aztecNode, initialFundedAccounts, 1, 'pxe-b')); + } = await setupSecondaryPXE(aztecNode, additionallyFundedAccounts, 1, 'pxe-b')); await walletA.registerSender(accountBAddress, 'accountB'); await walletB.registerSender(accountAAddress, 'accountA'); @@ -188,7 +192,7 @@ describe('e2e_2_pxes', () => { const transferAmount2 = 323n; // setup an account that is shared across PXEs - const sharedAccount = initialFundedAccounts[2]; + const sharedAccount = additionallyFundedAccounts[2]; const sharedAccountOnAManager = await walletA.createSchnorrAccount(sharedAccount.secret, sharedAccount.salt); const sharedAccountOnADeployMethod = await sharedAccountOnAManager.getDeployMethod(); await sharedAccountOnADeployMethod.send({ from: NO_FROM }); @@ -232,7 +236,7 @@ describe('e2e_2_pxes', () => { wallet: walletC, address: accountCAddress, teardown: teardownC, - } = await setupSecondaryPXE(aztecNode, initialFundedAccounts, 2, 'pxe-c'); + } = await setupSecondaryPXE(aztecNode, additionallyFundedAccounts, 2, 'pxe-c'); await walletC.registerContract(instance, TokenContract.artifact); // Transfer from A to C 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 74a85e77875c..481de92a955b 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 @@ -63,17 +63,12 @@ const itShouldBehaveLikeAnAccountContract = ( ({ logger, teardown, aztecNode } = await setup(0, { ...AUTOMINE_E2E_OPTS, - initialFundedAccounts: [accountData], + additionallyFundedAccounts: [accountData], })); wallet = await TestWalletInternals.create(aztecNode); const accountManager = await wallet.createAccount({ secret, contract, salt }); completeAddress = await accountManager.getCompleteAddress(); - 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: NO_FROM }); - } ({ contract: child } = await ChildContract.deploy(wallet).send({ from: address })); }); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts index 8c6dab4e7dc2..1639de4c38d3 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts @@ -17,7 +17,6 @@ import { jest } from '@jest/globals'; import { type EndToEndContext, type SetupOptions, - createFundedAccounts, ensureAuthRegistryPublished, setup, teardown, @@ -95,22 +94,11 @@ export class BlacklistTokenContractTest { // proxy deploys exceed the original window. jest.setTimeout(600_000); - this.logger.info('Deploying 3 accounts'); - const { accounts } = await createFundedAccounts( - 3, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - this.cheatCodes = this.context.cheatCodes; this.aztecNode = this.context.aztecNodeService; this.sequencer = this.context.sequencer!; this.wallet = this.context.wallet; - this.adminAddress = accounts[0].address; - this.otherAddress = accounts[1].address; - this.blacklistedAddress = accounts[2].address; + [this.adminAddress, this.otherAddress, this.blacklistedAddress] = this.context.accounts; this.logger.info('Setting up blacklist token contract'); await ensureAuthRegistryPublished(this.wallet, this.adminAddress); @@ -153,7 +141,7 @@ export class BlacklistTokenContractTest { async setup(opts: Partial = {}) { this.logger.info('Setting up fresh context'); - this.context = await setup(0, { + this.context = await setup(3, { ...opts, fundSponsoredFPC: true, }); 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 b5b2645777e0..a66733f457f7 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 { generateSchnorrAccounts } from '@aztec/accounts/testing'; 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'; @@ -514,11 +515,15 @@ describe('e2e_block_building', () => { // Regression for https://github.com/AztecProtocol/aztec-packages/issues/7537 it('sends a tx on the first block', async () => { - const context = await setup(0, { ...PIPELINING_SETUP_OPTS, minTxsPerBlock: 0, numberOfInitialFundedAccounts: 1 }); + const context = await setup(0, { + ...PIPELINING_SETUP_OPTS, + minTxsPerBlock: 0, + additionallyFundedAccounts: await generateSchnorrAccounts(1, 'schnorr'), + }); ({ teardown, logger, aztecNode, wallet } = context); await sleep(1000); - const [accountData] = context.initialFundedAccounts; + const [accountData] = context.additionallyFundedAccounts; const accountManager = await (wallet as TestWallet).createSchnorrAccount(accountData.secret, accountData.salt); const deployMethod = await accountManager.getDeployMethod(); @@ -563,7 +568,7 @@ describe('e2e_block_building', () => { // which translates in an incorrect end state for world state. We can easily detect this by checking whether the nullifier // tree next available leaf index is a multiple of 64. it('clears up all nullifiers if tx processing fails', async () => { - const context = await setup(1, { ...PIPELINING_SETUP_OPTS, minTxsPerBlock: 1, numberOfInitialFundedAccounts: 1 }); + const context = await setup(1, { ...PIPELINING_SETUP_OPTS, minTxsPerBlock: 1 }); ({ teardown, logger, 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 3fa1e83524ae..f10aa3db522e 100644 --- a/yarn-project/end-to-end/src/e2e_bot.test.ts +++ b/yarn-project/end-to-end/src/e2e_bot.test.ts @@ -38,7 +38,7 @@ describe('e2e_bot', () => { const setupResult = await setup(0, { ...PIPELINING_SETUP_OPTS, aztecProofSubmissionEpochs: 640, - initialFundedAccounts: [botAccount], + additionallyFundedAccounts: [botAccount], }); ({ teardown, diff --git a/yarn-project/end-to-end/src/e2e_card_game.test.ts b/yarn-project/end-to-end/src/e2e_card_game.test.ts index 5c6741a707b5..1c9b9a00653a 100644 --- a/yarn-project/end-to-end/src/e2e_card_game.test.ts +++ b/yarn-project/end-to-end/src/e2e_card_game.test.ts @@ -1,3 +1,4 @@ +import { generateSchnorrAccounts } from '@aztec/accounts/testing'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import type { GrumpkinScalar } from '@aztec/aztec.js/fields'; import { computeAppNullifierHidingKey, deriveMasterNullifierHidingKey } from '@aztec/aztec.js/keys'; @@ -11,6 +12,7 @@ import { jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; import { setup } from './fixtures/utils.js'; +import type { TestWallet } from './test-wallet/test_wallet.js'; /* eslint-disable camelcase */ @@ -60,7 +62,7 @@ describe('e2e_card_game', () => { let logger: Logger; let teardown: () => Promise; - let wallet: Wallet; + let wallet: TestWallet; let masterNullifierHidingKeys: GrumpkinScalar[]; let firstPlayer: AztecAddress; @@ -87,14 +89,18 @@ describe('e2e_card_game', () => { }; beforeAll(async () => { - const context = await setup(3, { ...AUTOMINE_E2E_OPTS }); + // This test derives nullifier-hiding keys from the players' secrets, so we provide and create the + // accounts ourselves. + const players = await generateSchnorrAccounts(3); + const context = await setup(0, { ...AUTOMINE_E2E_OPTS, additionallyFundedAccounts: players }); ({ logger, teardown, wallet } = context); - [firstPlayer, secondPlayer, thirdPlayer] = context.accounts; + for (const player of players) { + await wallet.createSchnorrInitializerlessAccount(player.secret, player.salt, player.signingKey); + } + [firstPlayer, secondPlayer, thirdPlayer] = players.map(p => p.address); - masterNullifierHidingKeys = context.initialFundedAccounts.map(({ secret }) => - deriveMasterNullifierHidingKey(secret), - ); + masterNullifierHidingKeys = players.map(({ secret }) => deriveMasterNullifierHidingKey(secret)); }); beforeEach(async () => { diff --git a/yarn-project/end-to-end/src/e2e_contract_updates.test.ts b/yarn-project/end-to-end/src/e2e_contract_updates.test.ts index ab2aa16ac1ee..0780a440151c 100644 --- a/yarn-project/end-to-end/src/e2e_contract_updates.test.ts +++ b/yarn-project/end-to-end/src/e2e_contract_updates.test.ts @@ -1,9 +1,8 @@ -import { getSchnorrAccountContractAddress } from '@aztec/accounts/schnorr'; +import { getSchnorrInitializerlessAccountContractAddress } from '@aztec/accounts/schnorr'; import { fastForwardContractUpdate, getContractClassFromArtifact } from '@aztec/aztec.js/contracts'; import { publishContractClass } from '@aztec/aztec.js/deployment'; import { Fr } from '@aztec/aztec.js/fields'; import type { AztecNode } from '@aztec/aztec.js/node'; -import type { Wallet } from '@aztec/aztec.js/wallet'; import type { CheatCodes } from '@aztec/aztec/testing'; import { MINIMUM_UPDATE_DELAY, UPDATED_CLASS_IDS_SLOT } from '@aztec/constants'; import { UpdatableContract } from '@aztec/noir-test-contracts.js/Updatable'; @@ -23,6 +22,7 @@ import { PublicDataTreeLeaf } from '@aztec/stdlib/trees'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; import { setup } from './fixtures/utils.js'; +import type { TestWallet } from './test-wallet/test_wallet.js'; // Set the update delay in genesis data so it's feasible to test in an e2e test. // The protocol enforces `MINIMUM_UPDATE_DELAY` (600 seconds, see constants.gen.ts), so we use that @@ -35,7 +35,7 @@ const INITIAL_UPDATABLE_CONTRACT_VALUE = 1n; const UPDATED_CONTRACT_PUBLIC_VALUE = 27n; describe('e2e_contract_updates', () => { - let wallet: Wallet; + let wallet: TestWallet; let defaultAccountAddress: AztecAddress; let teardown: () => Promise; let contract: UpdatableContract; @@ -80,29 +80,26 @@ describe('e2e_contract_updates', () => { const senderPrivateKey = Fr.random(); const signingKey = deriveSigningKey(senderPrivateKey); const salt = Fr.ONE; - const initialFundedAccounts = [ - { - secret: senderPrivateKey, - signingKey, - salt, - address: await getSchnorrAccountContractAddress(senderPrivateKey, salt, signingKey), - }, - ]; + // Use a deterministic initializerless account whose address we know before setup, so the scheduled + // delay can be seeded in genesis public data for it. We fund it and create it ourselves below. + const account = { + secret: senderPrivateKey, + signingKey, + salt, + type: 'schnorr_initializerless' as const, + address: await getSchnorrInitializerlessAccountContractAddress(senderPrivateKey, salt, signingKey), + }; + defaultAccountAddress = account.address; const constructorArgs = [INITIAL_UPDATABLE_CONTRACT_VALUE]; - const genesisPublicData = await setupScheduledDelay(constructorArgs, salt, initialFundedAccounts[0].address); - - ({ - aztecNode, - teardown, - wallet, - accounts: [defaultAccountAddress], - cheatCodes, - } = await setup(1, { + const genesisPublicData = await setupScheduledDelay(constructorArgs, salt, account.address); + + ({ aztecNode, teardown, wallet, cheatCodes } = await setup(0, { ...AUTOMINE_E2E_OPTS, genesisPublicData, - initialFundedAccounts, + additionallyFundedAccounts: [account], })); + await wallet.createSchnorrInitializerlessAccount(account.secret, account.salt, account.signingKey); ({ contract, instance } = await UpdatableContract.deploy(wallet, constructorArgs[0], { salt }).send({ from: defaultAccountAddress, diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts index 8860f0a4b39e..fe10607d1a8f 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts @@ -26,7 +26,6 @@ import { MNEMONIC } from '../fixtures/fixtures.js'; import { type EndToEndContext, type SetupOptions, - createFundedAccounts, ensureAuthRegistryPublished, setup, teardown, @@ -94,7 +93,7 @@ export class CrossChainMessagingTest { // Recompute requireEpochProven from the merged options so per-call startProverNode is honored. this.requireEpochProven = opts.startProverNode ?? this.setupOptions.startProverNode ?? false; this.context = await setup( - 0, + 3, { ...this.setupOptions, ...opts, @@ -156,16 +155,7 @@ export class CrossChainMessagingTest { await this.epochTestSettler.start(); } - // Deploy 3 accounts - this.logger.info('Applying 3_accounts setup'); - const { accounts } = await createFundedAccounts( - 3, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - [this.ownerAddress, this.user1Address, this.user2Address] = accounts.map(a => a.address); + [this.ownerAddress, this.user1Address, this.user2Address] = this.context.accounts; // Set up cross chain messaging this.logger.info('Applying e2e_cross_chain_messaging setup'); diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts index fa214c4c181b..abf70734c12d 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_test.ts @@ -9,7 +9,7 @@ import type { Wallet } from '@aztec/aztec.js/wallet'; import type { StatefulTestContract } from '@aztec/noir-test-contracts.js/StatefulTest'; import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; -import { type EndToEndContext, type SetupOptions, createFundedAccounts, setup, teardown } from '../fixtures/setup.js'; +import { type EndToEndContext, type SetupOptions, setup, teardown } from '../fixtures/setup.js'; import type { TestWallet } from '../test-wallet/test_wallet.js'; export class DeployTest { @@ -26,14 +26,14 @@ export class DeployTest { async setup(opts: Partial = {}) { this.logger.info('Setting up test environment'); - this.context = await setup(0, { + this.context = await setup(1, { ...opts, fundSponsoredFPC: true, }); this.aztecNode = this.context.aztecNodeService; this.wallet = this.context.wallet; this.aztecNodeAdmin = this.context.aztecNodeService; - await this.applyInitialAccount(); + this.defaultAccountAddress = this.context.accounts[0]; return this; } @@ -41,18 +41,6 @@ export class DeployTest { await teardown(this.context); } - private async applyInitialAccount() { - this.logger.info('Applying initial account setup'); - const { accounts } = await createFundedAccounts( - 1, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - this.defaultAccountAddress = accounts[0].address; - } - async registerContract( wallet: Wallet, contractArtifact: ContractArtifactClass, diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index 604352e3ca00..d3070051c968 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -27,7 +27,6 @@ import { MNEMONIC, getPaddedMaxFeesPerGas } from '../fixtures/fixtures.js'; import { type EndToEndContext, type SetupOptions, - createFundedAccounts, ensureAuthRegistryPublished, setup, teardown, @@ -108,7 +107,7 @@ export class FeesTest { this.logger.verbose('Setting up fresh context...'); // Token allowlist entries are test-only: FPC-based fee payment with custom tokens won't work on mainnet alpha. const tokenAllowList = await getTokenAllowedSetupFunctions(); - this.context = await setup(0, { + this.context = await setup(this.numberOfAccounts, { startProverNode: true, ...this.setupOptions, ...opts, @@ -189,14 +188,6 @@ export class FeesTest { async applyInitialAccounts() { this.logger.info('Applying initial accounts setup'); - const { accounts } = await createFundedAccounts( - this.numberOfAccounts, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - this.wallet = this.context.wallet; this.aztecNode = this.context.aztecNodeService; this.aztecNodeAdmin = this.context.aztecNodeService; @@ -204,7 +195,7 @@ export class FeesTest { maxFeesPerGas: await getPaddedMaxFeesPerGas(this.aztecNode), }); this.cheatCodes = this.context.cheatCodes; - this.accounts = accounts.map(a => a.address); + this.accounts = this.context.accounts; this.accounts.forEach((a, i) => this.logger.verbose(`Account ${i} address: ${a}`)); [this.aliceAddress, this.bobAddress, this.sequencerAddress] = this.accounts.slice(0, 3); diff --git a/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts b/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts index d88c17e9ebbd..d9a96bb69f8d 100644 --- a/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts +++ b/yarn-project/end-to-end/src/e2e_genesis_timestamp.test.ts @@ -1,3 +1,4 @@ +import { generateSchnorrAccounts } from '@aztec/accounts/testing'; import { NO_FROM } from '@aztec/aztec.js/account'; import { createLogger } from '@aztec/aztec.js/log'; import { retryUntil } from '@aztec/foundation/retry'; @@ -12,19 +13,28 @@ describe('e2e_genesis_timestamp', () => { const logger = createLogger('e2e:genesis_timestamp'); beforeEach(async () => { - // Skip account deployment and configure PXE to sync its anchor only to proven blocks so its + // Configure PXE to sync its anchor only to proven blocks so its // anchor lags behind proposed blocks. Under AUTOMINE_E2E_OPTS the AnvilTestWatcher is disabled // and the AutomineSequencer never marks blocks as proven on its own, so without a prover node // the proven tip stays at genesis for the duration of the test. - context = await setup(0, { ...AUTOMINE_E2E_OPTS, advancePastGenesis: false }, { syncChainTip: 'proven' }); + context = await setup( + 0, + { + ...AUTOMINE_E2E_OPTS, + advancePastGenesis: false, + // This test proves genesis-anchored account deployment txs, so it needs deployable accounts + additionallyFundedAccounts: await generateSchnorrAccounts(2, 'schnorr'), + }, + { syncChainTip: 'proven' }, + ); }); afterEach(() => context.teardown()); // Creates and proves a tx, and asserts it's anchored to the genesis block const proveTxAnchoredToGenesis = async (accountIndex = 0) => { - const { wallet, initialFundedAccounts } = context; - const { secret, salt, signingKey } = initialFundedAccounts[accountIndex]; + const { wallet, additionallyFundedAccounts } = context; + const { secret, salt, signingKey } = additionallyFundedAccounts[accountIndex]; const accountManager = await wallet.createSchnorrAccount(secret, salt, signingKey); const deployMethod = await accountManager.getDeployMethod(); const provenTx = await proveInteraction(wallet, deployMethod, { diff --git a/yarn-project/end-to-end/src/e2e_keys.test.ts b/yarn-project/end-to-end/src/e2e_keys.test.ts index 0890b1c721ed..a12b02623c2c 100644 --- a/yarn-project/end-to-end/src/e2e_keys.test.ts +++ b/yarn-project/end-to-end/src/e2e_keys.test.ts @@ -1,8 +1,7 @@ -import type { InitialAccountData } from '@aztec/accounts/testing'; +import { generateSchnorrAccounts } from '@aztec/accounts/testing'; import type { AztecAddress } from '@aztec/aztec.js/addresses'; import { Fr } from '@aztec/aztec.js/fields'; import type { AztecNode } from '@aztec/aztec.js/node'; -import type { Wallet } from '@aztec/aztec.js/wallet'; import { DomainSeparator, INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto/poseidon'; @@ -21,6 +20,7 @@ import { jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; import { setup } from './fixtures/utils.js'; +import type { TestWallet } from './test-wallet/test_wallet.js'; const TIMEOUT = 300_000; @@ -33,22 +33,18 @@ describe('Keys', () => { let testContract: TestContract; let secret: Fr; - let wallet: Wallet; + let wallet: TestWallet; let defaultAccountAddress: AztecAddress; beforeAll(async () => { - let initialFundedAccounts: InitialAccountData[]; - ({ - aztecNode, - teardown, - wallet, - accounts: [defaultAccountAddress], - initialFundedAccounts, - } = await setup(1, { ...AUTOMINE_E2E_OPTS })); + // This test needs the account's secret, so we provide and create the account ourselves. + const [account] = await generateSchnorrAccounts(1); + ({ aztecNode, teardown, wallet } = await setup(0, { ...AUTOMINE_E2E_OPTS, additionallyFundedAccounts: [account] })); + await wallet.createSchnorrInitializerlessAccount(account.secret, account.salt, account.signingKey); + defaultAccountAddress = account.address; + secret = account.secret; ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: defaultAccountAddress })); - - secret = initialFundedAccounts[0].secret; }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts index 165fb83c3baf..368766eb8f9b 100644 --- a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts +++ b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts @@ -1,4 +1,4 @@ -import type { InitialAccountData } from '@aztec/accounts/testing'; +import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; import type { Archiver } from '@aztec/archiver'; import type { AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; import { NO_FROM } from '@aztec/aztec.js/account'; @@ -42,7 +42,7 @@ describe('e2e_multi_validator_node', () => { let teardown: () => Promise; let wallet: TestWallet; let ownerAddress: AztecAddress; - let initialFundedAccounts: InitialAccountData[]; + let additionallyFundedAccounts: InitialAccountData[]; let aztecNode: AztecNode; let config: AztecNodeConfig; let logger: Logger; @@ -75,7 +75,7 @@ describe('e2e_multi_validator_node', () => { }); const { aztecSlotDuration: _aztecSlotDuration } = getL1ContractsConfigEnvVars(); - ({ teardown, logger, wallet, initialFundedAccounts, aztecNode, config, deployL1ContractsValues, cheatCodes } = + ({ teardown, logger, wallet, additionallyFundedAccounts, aztecNode, config, deployL1ContractsValues, cheatCodes } = await setup( 0, { @@ -87,6 +87,8 @@ describe('e2e_multi_validator_node', () => { sequencerPollingIntervalMS: 200, worldStateBlockCheckIntervalMS: 200, blockCheckIntervalMS: 200, + // We deploy this account ourselves later, so fund it as a regular (deployable) schnorr account. + additionallyFundedAccounts: await generateSchnorrAccounts(1, 'schnorr'), }, { syncChainTip: 'checkpointed' }, )); @@ -113,7 +115,7 @@ describe('e2e_multi_validator_node', () => { }); const deployOwnerAccount = async () => { - const accountData = initialFundedAccounts[0]; + const accountData = additionallyFundedAccounts[0]; const accountManager = await wallet.createSchnorrAccount( accountData.secret, accountData.salt, diff --git a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts index b8948b1c5f46..ba3b05b5d82a 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts @@ -1,16 +1,16 @@ -import { getSchnorrAccountContractAddress } from '@aztec/accounts/schnorr'; +import { getSchnorrInitializerlessAccountContractAddress } from '@aztec/accounts/schnorr'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { Fr, GrumpkinScalar } from '@aztec/aztec.js/fields'; import type { Logger } from '@aztec/aztec.js/log'; -import type { Wallet } from '@aztec/aztec.js/wallet'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; import { deployToken, expectTokenBalance } from './fixtures/token_utils.js'; import { setup } from './fixtures/utils.js'; +import type { TestWallet } from './test-wallet/test_wallet.js'; describe('e2e_multiple_accounts_1_enc_key', () => { - let wallet: Wallet; + let wallet: TestWallet; let accounts: AztecAddress[] = []; let logger: Logger; let teardown: () => Promise; @@ -24,26 +24,33 @@ describe('e2e_multiple_accounts_1_enc_key', () => { // A shared secret for all accounts. const secret = Fr.random(); - const initialFundedAccounts = await Promise.all( + // These accounts share one encryption key but use different signing keys, so we build and create them + // ourselves (the default setup accounts each use a distinct secret). They are initializerless. + const accountsData = await Promise.all( Array.from({ length: numAccounts }).map(async () => { // A different signing key for each account. const signingKey = GrumpkinScalar.random(); const salt = Fr.random(); - const address = await getSchnorrAccountContractAddress(secret, salt, signingKey); + const address = await getSchnorrInitializerlessAccountContractAddress(secret, salt, signingKey); return { secret, signingKey, salt, + type: 'schnorr_initializerless' as const, address, }; }), ); - ({ teardown, logger, wallet, accounts } = await setup(numAccounts, { + ({ teardown, logger, wallet } = await setup(0, { ...AUTOMINE_E2E_OPTS, - initialFundedAccounts, + additionallyFundedAccounts: accountsData, })); - logger.info('Account contracts deployed'); + for (const a of accountsData) { + await wallet.createSchnorrInitializerlessAccount(a.secret, a.salt, a.signingKey); + } + accounts = accountsData.map(a => a.address); + logger.info('Account contracts created'); ({ contract: token } = await deployToken(wallet, accounts[0], initialBalance, logger)); }); diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts index bb4e60fef6ad..457015188120 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts @@ -5,13 +5,7 @@ import type { Wallet } from '@aztec/aztec.js/wallet'; import { ChildContract } from '@aztec/noir-test-contracts.js/Child'; import { ParentContract } from '@aztec/noir-test-contracts.js/Parent'; -import { - type EndToEndContext, - type SetupOptions, - createFundedAccounts, - setup, - teardown as teardownSubsystems, -} from '../fixtures/setup.js'; +import { type EndToEndContext, type SetupOptions, setup, teardown as teardownSubsystems } from '../fixtures/setup.js'; export class NestedContractTest { context!: EndToEndContext; @@ -30,30 +24,16 @@ export class NestedContractTest { this.logger = createLogger(`e2e:e2e_nested_contract:${testName}`); } - /** - * Applies base setup by deploying accounts and publicly deploying them. - */ - async applyBaseSetup() { - this.logger.info('Deploying accounts'); - const { accounts } = await createFundedAccounts( - this.numberOfAccounts, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - this.wallet = this.context.wallet; - [{ address: this.defaultAccountAddress }] = accounts; - this.aztecNode = this.context.aztecNodeService; - } - async setup(opts: Partial = {}) { this.logger.info('Setting up fresh subsystems'); - this.context = await setup(0, { + // setup creates `numberOfAccounts` initializerless accounts, available on the context. + this.context = await setup(this.numberOfAccounts, { ...opts, fundSponsoredFPC: true, }); - await this.applyBaseSetup(); + this.wallet = this.context.wallet; + this.defaultAccountAddress = this.context.accounts[0]; + this.aztecNode = this.context.aztecNodeService; } async teardown() { 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 5a043873ced3..c222a06dd5b9 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 @@ -431,7 +431,7 @@ describe('e2e_p2p_add_rollup', () => { await bridging( nodes[0], - t.ctx.initialFundedAccounts[0], + t.ctx.additionallyFundedAccounts[0], t.ctx.deployL1ContractsValues.l1Client, t.ctx.deployL1ContractsValues.l1ContractAddresses, BigInt(t.ctx.aztecNodeConfig.rollupVersion), diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index 2dbd1044e6e2..8c3691b6c026 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -125,7 +125,6 @@ export class P2PNetworkTest { slasherEnabled: initialValidatorConfig.slasherEnabled ?? true, aztecTargetCommitteeSize: numberOfValidators, metricsPort: metricsPort, - numberOfInitialFundedAccounts: 2, startProverNode, }; @@ -368,8 +367,8 @@ export class P2PNetworkTest { address: await getAccountContractAddress(contract, secret, salt), }; - // Generate regular Schnorr accounts for tests that need deployable accounts (e.g. add_rollup). - const regularAccounts = await generateSchnorrAccounts(this.setupOptions.numberOfInitialFundedAccounts ?? 2); + // Generate funded (initializerless) Schnorr accounts for tests that create accounts from them (e.g. add_rollup). + const fundedAccounts = await generateSchnorrAccounts(2); this.context = await setup( 0, @@ -377,7 +376,7 @@ export class P2PNetworkTest { ...this.setupOptions, fundSponsoredFPC: true, skipInitialSequencer: true, - initialFundedAccounts: [...regularAccounts, this.hardcodedAccountData], + additionallyFundedAccounts: [...fundedAccounts, this.hardcodedAccountData], slasherEnabled: this.setupOptions.slasherEnabled ?? this.deployL1ContractsArgs.slasherEnabled ?? false, aztecTargetCommitteeSize: 0, l1ContractsArgs: this.deployL1ContractsArgs, @@ -388,7 +387,7 @@ export class P2PNetworkTest { this.ctx = this.context; const sponsoredFPCAddress = await getSponsoredFPCAddress(); - const initialFundedAccounts = [...this.context.initialFundedAccounts.map(a => a.address), sponsoredFPCAddress]; + const initialFundedAccounts = [...this.context.additionallyFundedAccounts.map(a => a.address), sponsoredFPCAddress]; const { genesis } = await getGenesisValues( initialFundedAccounts, diff --git a/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts b/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts index a8eb16753b3f..f128bb6c8984 100644 --- a/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_testnet/e2e_public_testnet_transfer.test.ts @@ -29,10 +29,9 @@ describe(`deploys and transfers a private only token`, () => { const chain = chainId == sepolia.id ? sepolia : foundry; // Not the best way of doing this. let accounts: AztecAddress[]; ({ logger, teardown, wallet, accounts } = await setup( - 2, // Deploy 2 accounts. + 2, // Create + fund 2 accounts. { ...PIPELINING_SETUP_OPTS, - numberOfInitialFundedAccounts: 2, // Fund 2 accounts. stateLoad: undefined, }, {}, 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 5155ad52b44d..341e24acb305 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 @@ -38,7 +38,7 @@ describe('e2e_sequencer_config', () => { ...PIPELINING_SETUP_OPTS, maxL2BlockGas: manaTarget * 2, manaTarget: BigInt(manaTarget), - initialFundedAccounts: [botAccount], + additionallyFundedAccounts: [botAccount], })); config = { ...getBotDefaultConfig(), 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 ed4cfb10adbe..c7541d76ecbc 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -31,7 +31,7 @@ * checkpointCount: 10, txCount: 36, complexity: PublicTransfer: {"numberOfBlocks":18, "syncTime":21.340179460525512} * checkpointCount: 10, txCount: 9, complexity: Spam: {"numberOfBlocks":17, "syncTime":49.40888188171387} */ -import type { InitialAccountData } from '@aztec/accounts/testing'; +import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; import { createArchiver } from '@aztec/archiver'; import { AztecNodeService } from '@aztec/aztec-node'; import { NO_FROM } from '@aztec/aztec.js/account'; @@ -338,13 +338,15 @@ describe('e2e_synching', () => { aztecNode, wallet, accounts: [defaultAccountAddress], - initialFundedAccounts, + additionallyFundedAccounts, cheatCodes, } = await setup(1, { ...PIPELINING_SETUP_OPTS, l1StartTime: START_TIME, l2StartTime: START_TIME + 200 * ETHEREUM_SLOT_DURATION, - numberOfInitialFundedAccounts: variant.txCount + 1, + // These accounts are created+deployed as regular schnorr accounts (see deployAccounts), so fund them + // at their regular addresses. + additionallyFundedAccounts: await generateSchnorrAccounts(variant.txCount + 1, 'schnorr'), }); variant.setWallet(wallet); @@ -368,7 +370,7 @@ describe('e2e_synching', () => { sequencer?.updateConfig({ minTxsPerBlock: variant.txCount, maxTxsPerBlock: variant.txCount }); // The setup will mint tokens (private and public) - const accountsToBeDeployed = initialFundedAccounts.slice(1); // The first one has been deployed in setup. + const accountsToBeDeployed = additionallyFundedAccounts.slice(1); // The first one has been deployed in setup. await variant.setup(accountsToBeDeployed); for (let i = 0; i < variant.checkpointCount; i++) { @@ -421,12 +423,12 @@ describe('e2e_synching', () => { sequencer, watcher, wallet, - initialFundedAccounts, + additionallyFundedAccounts, dateProvider, } = await setup(0, { ...PIPELINING_SETUP_OPTS, l1StartTime: START_TIME, - numberOfInitialFundedAccounts: 10, + additionallyFundedAccounts: await generateSchnorrAccounts(10, 'schnorr'), }); await (aztecNode as any).stop(); @@ -493,7 +495,7 @@ describe('e2e_synching', () => { } await alternativeSync( - { deployL1ContractsValues, cheatCodes, config, logger, initialFundedAccounts, wallet }, + { deployL1ContractsValues, cheatCodes, config, logger, additionallyFundedAccounts, wallet }, variant, ); @@ -569,7 +571,9 @@ describe('e2e_synching', () => { const { wallet } = await setupPXEAndGetWallet(aztecNode!); variant.setWallet(wallet); - const defaultAccountAddress = (await variant.deployAccounts(opts.initialFundedAccounts!.slice(0, 1)))[0]; + const defaultAccountAddress = ( + await variant.deployAccounts(opts.additionallyFundedAccounts!.slice(0, 1)) + )[0]; contracts.push( ( diff --git a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts index a6d7eb25a024..7fe1047e2cff 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts @@ -10,7 +10,6 @@ import { jest } from '@jest/globals'; import { type EndToEndContext, type SetupOptions, - createFundedAccounts, ensureAuthRegistryPublished, setup, teardown, @@ -70,18 +69,9 @@ export class TokenContractTest { // Adding a timeout of 2 minutes in here such that it is propagated to the underlying tests jest.setTimeout(120_000); - this.logger.info('Applying base setup - deploying 3 accounts'); - const { accounts } = await createFundedAccounts( - 3, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - this.node = this.context.aztecNodeService; this.wallet = this.context.wallet; - [this.adminAddress, this.account1Address, this.account2Address] = accounts.map(acc => acc.address); + [this.adminAddress, this.account1Address, this.account2Address] = this.context.accounts; this.logger.info('Applying base setup - deploying token contract'); await ensureAuthRegistryPublished(this.wallet, this.adminAddress); @@ -122,7 +112,7 @@ export class TokenContractTest { } async setup(opts: Partial = {}) { - this.context = await setup(0, { + this.context = await setup(3, { ...opts, metricsPort: this.metricsPort, fundSponsoredFPC: true, diff --git a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts index 2b68efb95656..f812176ec703 100644 --- a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts @@ -1,4 +1,4 @@ -import type { InitialAccountData } from '@aztec/accounts/testing'; +import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; import { AztecNodeService } from '@aztec/aztec-node'; import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; import { type Logger, createLogger } from '@aztec/aztec.js/log'; @@ -25,7 +25,6 @@ import { getBBConfig } from './get_bb_config.js'; import { type EndToEndContext, type SetupOptions, - createFundedAccounts, getPrivateKeyFromIndex, getSponsoredFPCAddress, setup, @@ -88,17 +87,13 @@ export class FullProverTest { * Applies base setup: deploys 2 accounts and token contract. */ private async applyBaseSetup() { - this.logger.info('Applying base setup: deploying accounts'); - const { accounts } = await createFundedAccounts( - 2, - this.logger, - )({ - wallet: this.context.wallet, - initialFundedAccounts: this.context.initialFundedAccounts, - }); - this.fundedAccounts = accounts; - this.accounts = accounts.map(a => a.address); + this.logger.info('Applying base setup: registering funded accounts'); + this.fundedAccounts = this.context.additionallyFundedAccounts; + this.accounts = this.fundedAccounts.map(a => a.address); this.wallet = this.context.wallet; + for (const { secret, salt, signingKey } of this.fundedAccounts) { + await this.wallet.createSchnorrInitializerlessAccount(secret, salt, signingKey); + } this.logger.info('Applying base setup: deploying token contract'); const { contract: asset, instance } = await TokenContract.deploy( @@ -128,6 +123,8 @@ export class FullProverTest { startProverNode: true, coinbase: this.coinbase, fundSponsoredFPC: true, + // Fund 2 accounts that we register (initializerless) in both the main and the proven-PXE wallets below. + additionallyFundedAccounts: await generateSchnorrAccounts(2), l1ContractsArgs: { realVerifier: this.realProofs }, }); @@ -192,8 +189,13 @@ export class FullProverTest { await provenWallet.registerContract(this.fakeProofsAssetInstance, TokenContract.artifact); for (let i = 0; i < 2; i++) { - await provenWallet.createSchnorrAccount(this.fundedAccounts[i].secret, this.fundedAccounts[i].salt); - await this.wallet.createSchnorrAccount(this.fundedAccounts[i].secret, this.fundedAccounts[i].salt); + // Mirror the initializerless funded accounts (created via createFundedAccounts) in both wallets so + // their addresses match the genesis-funded ones. + await provenWallet.createSchnorrInitializerlessAccount( + this.fundedAccounts[i].secret, + this.fundedAccounts[i].salt, + ); + await this.wallet.createSchnorrInitializerlessAccount(this.fundedAccounts[i].secret, this.fundedAccounts[i].salt); } const asset = TokenContract.at(this.fakeProofsAsset.address, provenWallet); @@ -221,7 +223,7 @@ export class FullProverTest { this.logger.verbose('Starting prover node'); const sponsoredFPCAddress = await getSponsoredFPCAddress(); const { genesis } = await getGenesisValues( - this.context.initialFundedAccounts.map(a => a.address).concat(sponsoredFPCAddress), + this.context.additionallyFundedAccounts.map(a => a.address).concat(sponsoredFPCAddress), undefined, undefined, this.context.genesis!.genesisTimestamp, diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index bfb746f5779e..54e1e668acf5 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -155,10 +155,12 @@ export type SetupOptions = { deployL1ContractsValues?: DeployAztecL1ContractsReturnType; /** Initial fee juice for default accounts */ initialAccountFeeJuice?: Fr; - /** Number of initial accounts funded with fee juice */ - numberOfInitialFundedAccounts?: number; - /** Data of the initial funded accounts */ - initialFundedAccounts?: InitialAccountData[]; + /** + * Extra accounts to fund at genesis beyond the `numberOfAccounts` initializerless accounts that setup + * creates. Setup funds these but does NOT create or deploy them — the test creates/deploys them itself + * (e.g. as a regular deployable account, or registered in a second PXE). Exposed on the context. + */ + additionallyFundedAccounts?: InitialAccountData[]; /** An initial set of validators */ initialValidators?: (Operator & { privateKey: `0x${string}` })[]; /** Anvil Start time */ @@ -236,8 +238,8 @@ export type EndToEndContext = { config: AztecNodeConfig; /** The Aztec Node configuration (alias for config for backward compatibility). */ aztecNodeConfig: AztecNodeConfig; - /** The data for the initial funded accounts. */ - initialFundedAccounts: InitialAccountData[]; + /** Data for the extra accounts funded at genesis but not created by setup (the test creates/deploys them). */ + additionallyFundedAccounts: InitialAccountData[]; /** The wallet to be used. */ wallet: TestWallet; /** The wallets to be used. */ @@ -401,11 +403,12 @@ export async function setup( config.coinbase = EthAddress.fromString(publisherHdAccount.address); } - // Determine which addresses to fund in genesis - const initialFundedAccounts = - opts.initialFundedAccounts ?? - (await generateSchnorrAccounts(opts.numberOfInitialFundedAccounts ?? Math.max(numberOfAccounts, 10))); - const addressesToFund = initialFundedAccounts.map(a => a.address); + // The accounts setup creates itself: `numberOfAccounts` initializerless accounts, generated here and + // funded at genesis so they are immediately usable (initializerless accounts need no deployment tx). + const defaultAccounts = await generateSchnorrAccounts(numberOfAccounts); + // Extra accounts the test wants funded at genesis but will create/deploy itself. + const additionallyFundedAccounts = opts.additionallyFundedAccounts ?? []; + const addressesToFund = [...defaultAccounts, ...additionallyFundedAccounts].map(a => a.address); // Optionally fund the sponsored FPC if (opts.fundSponsoredFPC) { @@ -634,13 +637,12 @@ export async function setup( let accounts: AztecAddress[] = []; - // Account creation is a PXE-side operation (registration + a simulated store call) with no on-chain tx, - // so it is independent of the sequencer and runs even when the initial sequencer is not started. + // Create the default accounts. They are initializerless, so this is a PXE-side operation (registration + // + a simulated store call) with no on-chain tx, independent of the sequencer. if (numberOfAccounts > 0) { logger.info(`Creating ${numberOfAccounts} initializerless test accounts`); - const accountsData = initialFundedAccounts.slice(0, numberOfAccounts); - const accountManagers = await createFundedInitializerlessAccounts(wallet, accountsData); - accounts = accountManagers.map(accountManager => accountManager.address); + await createFundedInitializerlessAccounts(wallet, defaultAccounts); + accounts = defaultAccounts.map(a => a.address); } // Advancing past genesis needs a running sequencer to build the empty block; advancePastGenesis is @@ -663,12 +665,6 @@ export async function setup( sequencerClient.getSequencer().updateConfig({ minTxsPerBlock: originalMinTxsPerBlock }); } - if (initialFundedAccounts.length < numberOfAccounts) { - throw new Error( - `Unable to deploy ${numberOfAccounts} accounts. Only ${initialFundedAccounts.length} accounts were funded.`, - ); - } - const teardown = async () => { try { await tryStop(wallet, logger); @@ -709,7 +705,7 @@ export async function setup( aztecNodeConfig: config, dateProvider, deployL1ContractsValues, - initialFundedAccounts, + additionallyFundedAccounts, logger, mockGossipSubNetwork, genesis, @@ -933,25 +929,6 @@ export async function ensureHandshakeRegistryPublished(wallet: Wallet, from: Azt await wallet.registerContract(instance, HandshakeRegistryArtifact); } -/** - * Helper function to create the initial (genesis-funded) test accounts as initializerless accounts. - * Returns the account data that can be used by tests. Initializerless accounts have no deployment tx, so - * creating them only registers the instances in the wallet; they are funded via genesis at their addresses. - */ -export const createFundedAccounts = - (numberOfAccounts: number, logger: Logger) => - async ({ wallet, initialFundedAccounts }: { wallet: TestWallet; initialFundedAccounts: InitialAccountData[] }) => { - if (initialFundedAccounts.length < numberOfAccounts) { - throw new Error(`Cannot create more than ${initialFundedAccounts.length} initial accounts.`); - } - - logger.verbose('Creating initializerless accounts funded with fee juice...'); - const accounts = initialFundedAccounts.slice(0, numberOfAccounts); - await createFundedInitializerlessAccounts(wallet, accounts); - - return { accounts }; - }; - /** * Destroys the current context. */ diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 79c8a882a9d0..b1605928f266 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -7,7 +7,6 @@ export { type EndToEndContext, type SetupOptions, createAndSyncProverNode, - createFundedAccounts, ensureAuthRegistryPublished, ensurePublicChecksPublished, expectMapping, From e6338715349fe5a3299e2bc05feb17f49656f2ae Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Wed, 10 Jun 2026 14:30:41 +0200 Subject: [PATCH 10/23] fixes --- .../cli-wallet/src/cmds/create_account.ts | 4 +- .../src/cmds/import_test_accounts.ts | 6 ++- .../cli-wallet/src/utils/constants.ts | 8 +++- yarn-project/cli-wallet/src/utils/wallet.ts | 46 +++++++++++++++++-- .../src/e2e_account_contracts.test.ts | 8 +--- yarn-project/end-to-end/src/fixtures/setup.ts | 2 +- 6 files changed, 58 insertions(+), 16 deletions(-) diff --git a/yarn-project/cli-wallet/src/cmds/create_account.ts b/yarn-project/cli-wallet/src/cmds/create_account.ts index 0bf8ad99a8d5..6b4487267bb5 100644 --- a/yarn-project/cli-wallet/src/cmds/create_account.ts +++ b/yarn-project/cli-wallet/src/cmds/create_account.ts @@ -71,7 +71,9 @@ export async function createAccount( let txHash: TxHash | undefined; let txReceipt: TxReceipt | undefined; - if (!registerOnly) { + // Initializerless accounts have no deployment tx — creating one only registers it locally — so there is + // nothing to deploy on-chain. + if (!registerOnly && accountType !== 'schnorr_initializerless') { const { paymentMethod, gasSettings } = await feeOpts.toUserFeeOptions(aztecNode, wallet, address); const delegatedDeployment = deployer && !account.address.equals(deployer); diff --git a/yarn-project/cli-wallet/src/cmds/import_test_accounts.ts b/yarn-project/cli-wallet/src/cmds/import_test_accounts.ts index e6302e5aa10f..f531b91ed1c4 100644 --- a/yarn-project/cli-wallet/src/cmds/import_test_accounts.ts +++ b/yarn-project/cli-wallet/src/cmds/import_test_accounts.ts @@ -16,7 +16,11 @@ export async function importTestAccounts(wallet: CLIWallet, db: WalletDB, json: const secret = testAccounts[i].secret; const salt = new Fr(account.salt); const address = account.address; - await db.storeAccount(address, { type: 'schnorr', secretKey: secret, salt, alias, publicKey: undefined }, log); + await db.storeAccount( + address, + { type: 'schnorr_initializerless', secretKey: secret, salt, alias, publicKey: undefined }, + log, + ); if (json) { out[alias] = { diff --git a/yarn-project/cli-wallet/src/utils/constants.ts b/yarn-project/cli-wallet/src/utils/constants.ts index ab747206179a..ce5184c51584 100644 --- a/yarn-project/cli-wallet/src/utils/constants.ts +++ b/yarn-project/cli-wallet/src/utils/constants.ts @@ -1,4 +1,10 @@ export const MIN_FEE_PADDING = 0.5; -export const AccountTypes = ['schnorr', 'ecdsasecp256r1', 'ecdsasecp256r1ssh', 'ecdsasecp256k1'] as const; +export const AccountTypes = [ + 'schnorr', + 'schnorr_initializerless', + 'ecdsasecp256r1', + 'ecdsasecp256r1ssh', + 'ecdsasecp256k1', +] as const; export type AccountType = (typeof AccountTypes)[number]; diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index e4ff836941de..0e2eecdcf6dc 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -1,15 +1,21 @@ import { EcdsaRAccountContract, EcdsaRSSHAccountContract } from '@aztec/accounts/ecdsa'; import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/ecdsa/stub'; -import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; +import { SchnorrAccountContract, SchnorrInitializerlessAccountContract } from '@aztec/accounts/schnorr'; import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/schnorr/stub'; import { getIdentities } from '@aztec/accounts/utils'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; -import { type InteractionFeeOptions, getContractClassFromArtifact, getGasLimits } from '@aztec/aztec.js/contracts'; +import { + ContractFunctionInteraction, + type InteractionFeeOptions, + getContractClassFromArtifact, + getGasLimits, +} from '@aztec/aztec.js/contracts'; import type { AztecNode } from '@aztec/aztec.js/node'; import { AccountManager, type Aliased, type SimulateOptions } from '@aztec/aztec.js/wallet'; import { TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; import { DefaultEntrypoint } from '@aztec/entrypoints/default'; +import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { LogFn } from '@aztec/foundation/log'; import type { NotesFilter } from '@aztec/pxe/client/lazy'; @@ -77,6 +83,7 @@ export class CLIWallet extends BaseWallet { await this.pxe.registerContractClass(StubEcdsaAccountContractArtifact); this.stubClassIds.set('schnorr', schnorrClassId); + this.stubClassIds.set('schnorr_initializerless', schnorrClassId); this.stubClassIds.set('ecdsasecp256k1', ecdsaClassId); this.stubClassIds.set('ecdsasecp256r1', ecdsaClassId); this.stubClassIds.set('ecdsasecp256r1ssh', ecdsaClassId); @@ -144,14 +151,32 @@ export class CLIWallet extends BaseWallet { return account; } - private async createAccount(secret: Fr, salt: Fr, contract: AccountContract): Promise { - const accountManager = await AccountManager.create(this, secret, contract, { salt }); + private async createAccount( + secret: Fr, + salt: Fr, + contract: AccountContract, + isInitializerless = false, + ): Promise { + const init = isInitializerless ? await contract.getInitializationFunctionAndArgs() : undefined; + const immutablesHash = init ? await poseidon2Hash(init.constructorArgs) : undefined; + + const accountManager = await AccountManager.create(this, secret, contract, { salt, immutablesHash }); const instance = accountManager.getInstance(); const artifact = await contract.getContractArtifact(); await this.registerContract(instance, artifact, secret); this.accountCache.set(accountManager.address.toString(), await accountManager.getAccount()); + + if (init) { + const constructorAbi = artifact.functions.find(f => f.name === init.constructorName); + if (!constructorAbi) { + throw new Error('Could not create SchnorrInitializerlessAccount: constructor ABI not found'); + } + const storeCall = new ContractFunctionInteraction(this, instance.address, constructorAbi, init.constructorArgs); + await storeCall.simulate({ from: instance.address }); + } + return accountManager; } @@ -179,6 +204,15 @@ export class CLIWallet extends BaseWallet { account = await this.createAccount(secretKey, salt, new SchnorrAccountContract(deriveSigningKey(secretKey))); break; } + case 'schnorr_initializerless': { + account = await this.createAccount( + secretKey, + salt, + new SchnorrInitializerlessAccountContract(deriveSigningKey(secretKey)), + true, + ); + break; + } case 'ecdsasecp256r1': { account = await this.createAccount( secretKey, @@ -229,7 +263,9 @@ export class CLIWallet extends BaseWallet { } const { type } = await this.db!.retrieveAccount(address); const stubAccount = - type === 'schnorr' ? createStubSchnorrAccount(originalAddress) : createStubEcdsaAccount(originalAddress); + type === 'schnorr' || type === 'schnorr_initializerless' + ? createStubSchnorrAccount(originalAddress) + : createStubEcdsaAccount(originalAddress); const stubClassId = this.stubClassIds.get(type); if (!stubClassId) { throw new Error( 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 481de92a955b..dfe6fc6c4afe 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,12 +1,6 @@ import { EcdsaKAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { - type Account, - type AccountContract, - BaseAccount, - NO_FROM, - getAccountContractAddress, -} from '@aztec/aztec.js/account'; +import { type Account, type AccountContract, BaseAccount, 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'; diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index 54e1e668acf5..f732a96416d9 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -1,7 +1,7 @@ import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node'; import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses'; -import { type ContractMethod } from '@aztec/aztec.js/contracts'; +import type { ContractMethod } from '@aztec/aztec.js/contracts'; import { publishContractClass, publishInstance } from '@aztec/aztec.js/deployment'; import { Fr } from '@aztec/aztec.js/fields'; import { type Logger, createLogger } from '@aztec/aztec.js/log'; From 2f0df2dea10b853571079a50d46939e3395be71c Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 10 Jun 2026 14:43:30 +0000 Subject: [PATCH 11/23] more fixes --- .../src/main.nr | 59 ++++++++++++++++++- .../end-to-end/src/e2e_epochs/epochs_test.ts | 4 +- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr index e690abb788d7..49db1a6ec3d3 100644 --- a/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr @@ -21,11 +21,24 @@ unconstrained fn no_sync( #[aztec(AztecConfig::new().custom_sync_state(crate::no_sync))] pub contract SchnorrInitializerlessAccount { use aztec::{ - authwit::{account::AccountActions, entrypoint::app::AppPayload}, + authwit::{ + account::AccountActions, + auth::{compute_authwit_message_hash, compute_authwit_nullifier}, + entrypoint::app::AppPayload, + }, context::PrivateContext, macros::functions::{allow_phase_change, external, view}, - oracle::{auth_witness::get_auth_witness, capsules::{load, store}, get_contract_instance::get_contract_instance}, - protocol::{hash::{poseidon2_hash, poseidon2_hash_bytes}, traits::{Deserialize, Serialize}}, + oracle::{ + auth_witness::get_auth_witness, + capsules::{load, store}, + get_contract_instance::get_contract_instance, + get_nullifier_membership_witness::get_low_nullifier_membership_witness, + }, + protocol::{ + address::AztecAddress, + hash::{compute_siloed_nullifier, poseidon2_hash, poseidon2_hash_bytes}, + traits::{Deserialize, Serialize}, + }, }; use std::embedded_curve_ops::EmbeddedCurvePoint; @@ -87,4 +100,44 @@ pub contract SchnorrInitializerlessAccount { schnorr::verify_signature(public_key, signature, outer_hash) } + + /// @notice Helper function to check validity of private authwitnesses + /// @param consumer The address of the consumer of the message + /// @param message_hash The message hash of the message to check the validity + /// @return True if the message_hash can be consumed, false otherwise + #[external("utility")] + unconstrained fn lookup_validity(consumer: AztecAddress, inner_hash: Field) -> bool { + // Safety: The public key inside the capsule is checked to match the immutables_hash of this instance + let public_key = load(self.address, PUB_KEY_SLOT, self.address) + .map(|data| EmbeddedCurvePoint::deserialize(data)) + .unwrap_or_else(|| panic( + "Public key was not stored in the Private eXecution Environment. Please call `constructor` first", + )); + + let message_hash = compute_authwit_message_hash( + consumer, + self.context.chain_id(), + self.context.version(), + inner_hash, + ); + + let limbs: [Field; 4] = get_auth_witness(message_hash); + let signature = ( + std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[0], limbs[1]), + std::embedded_curve_ops::EmbeddedCurveScalar::new(limbs[2], limbs[3]), + ); + let pub_key = std::embedded_curve_ops::EmbeddedCurvePoint { x: public_key.x, y: public_key.y }; + let valid_in_private = schnorr::verify_signature(pub_key, signature, message_hash); + + // Compute the nullifier and check if it is spent + // This will BLINDLY TRUST the oracle, but the oracle is us, and + // it is not as part of execution of the contract, so we are good. + let nullifier = compute_authwit_nullifier(self.address, inner_hash); + let siloed_nullifier = compute_siloed_nullifier(consumer, nullifier); + let (low_leaf_preimage, _witness) = + get_low_nullifier_membership_witness(self.context.block_header(), siloed_nullifier); + let is_spent = low_leaf_preimage.nullifier == siloed_nullifier; + + !is_spent & valid_in_private + } } diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_test.ts index b172be919708..315c7a5b7718 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_test.ts @@ -161,7 +161,7 @@ export class EpochsTestContext { exitDelaySeconds: DefaultL1ContractsConfig.exitDelaySeconds, slasherEnabled: false, ...opts, - ...(hardcodedAccountData ? { initialFundedAccounts: [hardcodedAccountData], numberOfAccounts: 0 } : {}), + ...(hardcodedAccountData ? { additionallyFundedAccounts: [hardcodedAccountData], numberOfAccounts: 0 } : {}), }, // Use checkpointed chain tip for PXE by default to avoid issues with blocks being dropped due to pruned anchor blocks. // Can be overridden via opts.pxeOpts. @@ -219,7 +219,7 @@ export class EpochsTestContext { /** * Computes InitialAccountData for a SchnorrHardcodedKeyAccountContract. * This contract has a hardcoded signing key and no initializer, so it can be used without - * on-chain deployment. Pass the returned data in `initialFundedAccounts` so the address + * on-chain deployment. Pass the returned data in `additionallyFundedAccounts` so the address * gets funded with fee juice in genesis. */ public static async getHardcodedAccountData(secret: Fr, salt: Fr): Promise { From 9b62bd20afa09cbc44bb487567bf8f8d4c0630dc Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 10 Jun 2026 19:22:53 +0000 Subject: [PATCH 12/23] more fixes --- docs/examples/ts/aave_bridge/index.ts | 7 +++++-- docs/examples/ts/aztecjs_advanced/index.ts | 2 +- docs/examples/ts/aztecjs_authwit/index.ts | 2 +- docs/examples/ts/aztecjs_connection/index.ts | 16 +++++++++++---- .../ts/aztecjs_getting_started/index.ts | 4 ++-- .../ts/aztecjs_kernelless_simulation/index.ts | 2 +- docs/examples/ts/aztecjs_testing/index.ts | 16 ++++++++++++--- docs/examples/ts/bob_token_contract/index.ts | 13 ++++++------ docs/examples/ts/example_swap/index.ts | 12 ++++++++--- docs/examples/ts/token_bridge/index.ts | 7 +++++-- .../src/e2e_account_contracts.test.ts | 19 ++++++++++++++++-- .../src/e2e_circuit_recorder.test.ts | 20 +++++++++++++------ .../end-to-end/src/e2e_debug_trace.test.ts | 4 ++-- .../e2e_epochs/epochs_mbps.parallel.test.ts | 7 ++++++- .../end-to-end/src/e2e_multi_eoa.test.ts | 10 ++++++++++ 15 files changed, 105 insertions(+), 36 deletions(-) diff --git a/docs/examples/ts/aave_bridge/index.ts b/docs/examples/ts/aave_bridge/index.ts index 2d7a46053cfb..07e3a94eaca7 100644 --- a/docs/examples/ts/aave_bridge/index.ts +++ b/docs/examples/ts/aave_bridge/index.ts @@ -36,7 +36,7 @@ const node = createAztecNodeClient( await waitForNode(node); const aztecWallet = await EmbeddedWallet.create(node, { ephemeral: true }); const [accData] = await getInitialTestAccountsData(); -const account = await aztecWallet.createSchnorrAccount( +const account = await aztecWallet.createSchnorrInitializerlessAccount( accData.secret, accData.salt, accData.signingKey, @@ -279,7 +279,10 @@ console.log("Block proven!\n"); // Compute the membership witness using the message hash and the L2 tx hash. // The node picks the smallest partial-proof root that covers the tx's checkpoint. -const witness = await node.getL2ToL1MembershipWitness(exitReceipt.txHash, msgLeaf); +const witness = await node.getL2ToL1MembershipWitness( + exitReceipt.txHash, + msgLeaf, +); const epoch = witness!.epochNumber; const numCheckpointsInEpoch = witness!.numCheckpointsInEpoch; diff --git a/docs/examples/ts/aztecjs_advanced/index.ts b/docs/examples/ts/aztecjs_advanced/index.ts index fbcdcdb029db..40109b6ed676 100644 --- a/docs/examples/ts/aztecjs_advanced/index.ts +++ b/docs/examples/ts/aztecjs_advanced/index.ts @@ -26,7 +26,7 @@ const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return ( - await wallet.createSchnorrAccount( + await wallet.createSchnorrInitializerlessAccount( account.secret, account.salt, account.signingKey, diff --git a/docs/examples/ts/aztecjs_authwit/index.ts b/docs/examples/ts/aztecjs_authwit/index.ts index 565333a38b6d..4d7c06294539 100644 --- a/docs/examples/ts/aztecjs_authwit/index.ts +++ b/docs/examples/ts/aztecjs_authwit/index.ts @@ -16,7 +16,7 @@ const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return ( - await wallet.createSchnorrAccount( + await wallet.createSchnorrInitializerlessAccount( account.secret, account.salt, account.signingKey, diff --git a/docs/examples/ts/aztecjs_connection/index.ts b/docs/examples/ts/aztecjs_connection/index.ts index 4f2aeb2add75..4d4dca73efdf 100644 --- a/docs/examples/ts/aztecjs_connection/index.ts +++ b/docs/examples/ts/aztecjs_connection/index.ts @@ -24,7 +24,7 @@ const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return ( - await wallet.createSchnorrAccount( + await wallet.createSchnorrInitializerlessAccount( account.secret, account.salt, account.signingKey, @@ -158,7 +158,10 @@ import { FeeJuicePaymentMethodWithClaim } from "@aztec/aztec.js/fee"; // claim is from the bridgeTokensPublic step above // Create a payment method that claims the bridged Fee Juice and uses it to pay -const bridgePaymentMethod = new FeeJuicePaymentMethodWithClaim(feeJuiceAccount.address, claim); +const bridgePaymentMethod = new FeeJuicePaymentMethodWithClaim( + feeJuiceAccount.address, + claim, +); // Use it to pay for any transaction; here we deploy the account in one step const deployMethodBridged = await feeJuiceAccount.getDeployMethod(); @@ -175,5 +178,10 @@ const metadata = await wallet.getContractMetadata(newAccount.address); console.log("Account deployed:", metadata.initializationStatus); // docs:end:verify_account_deployment -const feeJuiceMetadata = await wallet.getContractMetadata(feeJuiceAccount.address); -console.log("Fee Juice account deployed:", feeJuiceMetadata.initializationStatus); +const feeJuiceMetadata = await wallet.getContractMetadata( + feeJuiceAccount.address, +); +console.log( + "Fee Juice account deployed:", + feeJuiceMetadata.initializationStatus, +); diff --git a/docs/examples/ts/aztecjs_getting_started/index.ts b/docs/examples/ts/aztecjs_getting_started/index.ts index 2338115e1e4b..db238d59d1e3 100644 --- a/docs/examples/ts/aztecjs_getting_started/index.ts +++ b/docs/examples/ts/aztecjs_getting_started/index.ts @@ -6,8 +6,8 @@ const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true }); const [alice, bob] = await getInitialTestAccountsData(); -await wallet.createSchnorrAccount(alice.secret, alice.salt); -await wallet.createSchnorrAccount(bob.secret, bob.salt); +await wallet.createSchnorrInitializerlessAccount(alice.secret, alice.salt); +await wallet.createSchnorrInitializerlessAccount(bob.secret, bob.salt); // docs:end:setup // docs:start:deploy diff --git a/docs/examples/ts/aztecjs_kernelless_simulation/index.ts b/docs/examples/ts/aztecjs_kernelless_simulation/index.ts index c23a6222ebb8..b9b15f05846b 100644 --- a/docs/examples/ts/aztecjs_kernelless_simulation/index.ts +++ b/docs/examples/ts/aztecjs_kernelless_simulation/index.ts @@ -20,7 +20,7 @@ const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { return ( - await wallet.createSchnorrAccount( + await wallet.createSchnorrInitializerlessAccount( account.secret, account.salt, account.signingKey, diff --git a/docs/examples/ts/aztecjs_testing/index.ts b/docs/examples/ts/aztecjs_testing/index.ts index a7da6e351b20..07a77c47e164 100644 --- a/docs/examples/ts/aztecjs_testing/index.ts +++ b/docs/examples/ts/aztecjs_testing/index.ts @@ -16,13 +16,21 @@ let token: TokenContract; // beforeAll equivalent - setup async function setup() { - const node = createAztecNodeClient(process.env.AZTEC_NODE_URL ?? "http://localhost:8080"); + const node = createAztecNodeClient( + process.env.AZTEC_NODE_URL ?? "http://localhost:8080", + ); await waitForNode(node); wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const testAccounts = await getInitialTestAccountsData(); [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { - return (await wallet.createSchnorrAccount(account.secret, account.salt, account.signingKey)).address; + return ( + await wallet.createSchnorrInitializerlessAccount( + account.secret, + account.salt, + account.signingKey, + ) + ).address; }), ); @@ -61,7 +69,9 @@ async function testTransferTokens() { .send({ from: aliceAddress }); // Transfer to bob using public transfer - await token.methods.transfer_in_public(aliceAddress, bobAddress, 100n, 0n).send({ from: aliceAddress }); + await token.methods + .transfer_in_public(aliceAddress, bobAddress, 100n, 0n) + .send({ from: aliceAddress }); const { result: aliceBalance } = await token.methods .balance_of_public(aliceAddress) diff --git a/docs/examples/ts/bob_token_contract/index.ts b/docs/examples/ts/bob_token_contract/index.ts index 6ce79a32c124..7f929e090d71 100644 --- a/docs/examples/ts/bob_token_contract/index.ts +++ b/docs/examples/ts/bob_token_contract/index.ts @@ -58,18 +58,19 @@ async function main() { const [giggleWalletData, aliceWalletData, bobClinicWalletData] = await getInitialTestAccountsData(); - const giggleAccountManager = await wallet.createSchnorrAccount( + const giggleAccountManager = await wallet.createSchnorrInitializerlessAccount( giggleWalletData.secret, giggleWalletData.salt, ); - const aliceAccountManager = await wallet.createSchnorrAccount( + const aliceAccountManager = await wallet.createSchnorrInitializerlessAccount( aliceWalletData.secret, aliceWalletData.salt, ); - const bobClinicAccountManager = await wallet.createSchnorrAccount( - bobClinicWalletData.secret, - bobClinicWalletData.salt, - ); + const bobClinicAccountManager = + await wallet.createSchnorrInitializerlessAccount( + bobClinicWalletData.secret, + bobClinicWalletData.salt, + ); const giggleAddress = giggleAccountManager.address; const aliceAddress = aliceAccountManager.address; diff --git a/docs/examples/ts/example_swap/index.ts b/docs/examples/ts/example_swap/index.ts index 5a81e6308b88..5cc0838f523b 100644 --- a/docs/examples/ts/example_swap/index.ts +++ b/docs/examples/ts/example_swap/index.ts @@ -34,7 +34,7 @@ const node = createAztecNodeClient(nodeUrl); await waitForNode(node); const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const [accData] = await getInitialTestAccountsData(); -const account = await wallet.createSchnorrAccount( +const account = await wallet.createSchnorrInitializerlessAccount( accData.secret, accData.salt, accData.signingKey, @@ -445,7 +445,10 @@ const exitMsgLeaf = computeL2ToL1MessageHash({ // docs:start:consume_l1_messages_witnesses // The node picks the smallest partial-proof root that covers each tx's checkpoint. -const exitWitness = await node.getL2ToL1MembershipWitness(swapReceipt.txHash, exitMsgLeaf); +const exitWitness = await node.getL2ToL1MembershipWitness( + swapReceipt.txHash, + exitMsgLeaf, +); const exitSiblingPath = exitWitness!.siblingPath .toBufferArray() .map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`); @@ -495,7 +498,10 @@ const swapMsgLeaf = computeL2ToL1MessageHash({ chainId: new Fr(foundry.id), }); -const swapWitness = await node.getL2ToL1MembershipWitness(swapReceipt.txHash, swapMsgLeaf); +const swapWitness = await node.getL2ToL1MembershipWitness( + swapReceipt.txHash, + swapMsgLeaf, +); const swapSiblingPath = swapWitness!.siblingPath .toBufferArray() .map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`); diff --git a/docs/examples/ts/token_bridge/index.ts b/docs/examples/ts/token_bridge/index.ts index 4f9e4c1b4301..993a39498569 100644 --- a/docs/examples/ts/token_bridge/index.ts +++ b/docs/examples/ts/token_bridge/index.ts @@ -28,7 +28,7 @@ console.log("Setting up L2...\n"); const node = createAztecNodeClient("http://localhost:8080"); const aztecWallet = await EmbeddedWallet.create(node); const [accData] = await getInitialTestAccountsData(); -const account = await aztecWallet.createSchnorrAccount( +const account = await aztecWallet.createSchnorrInitializerlessAccount( accData.secret, accData.salt, ); @@ -304,7 +304,10 @@ console.log("Block proven!\n"); // Compute the membership witness using the message hash and the L2 tx hash. // The node picks the smallest partial-proof root that covers the tx's checkpoint. -const witness = await node.getL2ToL1MembershipWitness(exitReceipt.txHash, msgLeaf); +const witness = await node.getL2ToL1MembershipWitness( + exitReceipt.txHash, + msgLeaf, +); const epoch = witness!.epochNumber; const numCheckpointsInEpoch = witness!.numCheckpointsInEpoch; console.log(` Epoch for block ${exitReceipt.blockNumber}: ${epoch}`); 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 dfe6fc6c4afe..4635f190c272 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 { SchnorrAccountContract, SchnorrInitializerlessAccountContract } from '@aztec/accounts/schnorr'; +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'; @@ -64,6 +70,11 @@ const itShouldBehaveLikeAnAccountContract = ( const accountManager = await wallet.createAccount({ secret, contract, salt }); completeAddress = await accountManager.getCompleteAddress(); + if (await accountManager.hasInitializer()) { + const deployMethod = await accountManager.getDeployMethod(); + await deployMethod.send({ from: NO_FROM }); + } + ({ contract: child } = await ChildContract.deploy(wallet).send({ from: address })); }); @@ -102,6 +113,10 @@ describe('e2e_account_contracts', () => { itShouldBehaveLikeAnAccountContract(() => new SchnorrAccountContract(GrumpkinScalar.random())); }); + describe('schnorr initializerless account', () => { + itShouldBehaveLikeAnAccountContract(() => new SchnorrInitializerlessAccountContract(GrumpkinScalar.random())); + }); + describe('ecdsa stored-key account', () => { itShouldBehaveLikeAnAccountContract(() => new EcdsaKAccountContract(randomBytes(32))); }); diff --git a/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts b/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts index e806f61dfe0c..b307e1171c31 100644 --- a/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts +++ b/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts @@ -1,4 +1,5 @@ import { MAX_APPS_PER_KERNEL } from '@aztec/constants'; +import { ChildContract } from '@aztec/noir-test-contracts.js/Child'; import fs from 'fs/promises'; import path from 'path'; @@ -16,8 +17,15 @@ describe('Circuit Recorder', () => { // Set recording directory env var - this will activate the circuit recorder process.env.CIRCUIT_RECORD_DIR = RECORD_DIR; - // Run setup which deploys an account contract and runs kernels - const { teardown } = await setup(1, { ...AUTOMINE_E2E_OPTS }); + // setup creates an initializerless account, which has no deployment tx. Deploying a contract from + // it exercises the account entrypoint (a user circuit) and the private kernels (protocol circuits), + // which is what this test inspects below — without a pointless account deployment. + const { + teardown, + wallet, + accounts: [accountAddress], + } = await setup(1, { ...AUTOMINE_E2E_OPTS }); + await ChildContract.deploy(wallet).send({ from: accountAddress }); // Check recording directory exists const dirExists = await fs.stat(RECORD_DIR).then( @@ -26,20 +34,20 @@ describe('Circuit Recorder', () => { ); expect(dirExists).toBe(true); - // Check recording file of a user circuit (contract circuit) exists and has expected content + // Check recording file of a user circuit (the account contract entrypoint) exists and has expected content { const files = await fs.readdir(RECORD_DIR); expect(files.length).toBeGreaterThan(0); - const recordingFile = files.find(f => f.startsWith('SchnorrAccount_constructor')); + const recordingFile = files.find(f => f.startsWith('SchnorrInitializerlessAccount_entrypoint')); expect(recordingFile).toBeDefined(); const recordingContent = await fs.readFile(path.join(RECORD_DIR, recordingFile!), 'utf8'); const recording = JSON.parse(recordingContent); expect(recording).toMatchObject({ - circuitName: 'SchnorrAccount', - functionName: 'constructor', + circuitName: 'SchnorrInitializerlessAccount', + functionName: 'entrypoint', inputs: expect.any(Object), oracleCalls: expect.any(Array), }); diff --git a/yarn-project/end-to-end/src/e2e_debug_trace.test.ts b/yarn-project/end-to-end/src/e2e_debug_trace.test.ts index a9784e6aca9f..fbb3282245a2 100644 --- a/yarn-project/end-to-end/src/e2e_debug_trace.test.ts +++ b/yarn-project/end-to-end/src/e2e_debug_trace.test.ts @@ -120,7 +120,7 @@ describe('e2e_debug_trace_transaction', () => { } }); - expect(await aztecNode.getBlockNumber()).toBeGreaterThanOrEqual(2); + expect(await aztecNode.getBlockNumber()).toBeGreaterThanOrEqual(1); // The current config requires at least 1 tx per block, so the block number won't be increasing @@ -242,7 +242,7 @@ describe('e2e_debug_trace_transaction', () => { } }); - expect(await aztecNode.getBlockNumber()).toBeGreaterThanOrEqual(2); + expect(await aztecNode.getBlockNumber()).toBeGreaterThanOrEqual(1); const numBlocksToMine = 3; const startBlockNumber = await aztecNode.getBlockNumber(); diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts index 03df1e414ad5..bdca7c7cdd20 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts @@ -327,7 +327,12 @@ describe('e2e_epochs/epochs_mbps', () => { }); it('builds multiple blocks per slot with L1 to L2 messages', async () => { - await setupTest({ syncChainTip: 'proposed', minTxsPerBlock: 1, maxTxsPerBlock: 1 }); + // L1→L2 messages only become ready once the chain advances `inboxLag` checkpoints past where they + // were inboxed, and a checkpoint only advances when a block is built in a new slot. With + // skipInitialSequencer the chain won't move on its own, and a one-shot burst of filler txs lands + // within a single checkpoint — so let the sequencer keep building (empty) blocks each slot to drive + // the chain forward until the messages are ready. + await setupTest({ syncChainTip: 'proposed', minTxsPerBlock: 0, maxTxsPerBlock: 1, buildCheckpointIfEmpty: true }); // Start sequencers first, then deploy cross-chain contract (needs running sequencer to mine). await Promise.all(nodes.map(n => n.getSequencer()!.start())); diff --git a/yarn-project/end-to-end/src/e2e_multi_eoa.test.ts b/yarn-project/end-to-end/src/e2e_multi_eoa.test.ts index 727bd706883a..088006a88200 100644 --- a/yarn-project/end-to-end/src/e2e_multi_eoa.test.ts +++ b/yarn-project/end-to-end/src/e2e_multi_eoa.test.ts @@ -95,6 +95,16 @@ describe('e2e_multi_eoa', () => { sequencer = sequencerClient! as TestSequencerClient; publisherManager = sequencer.publisherManager; aztecNodeAdmin = maybeAztecNodeAdmin!; + + // Initializerless accounts deploy nothing during setup, so the chain sits at the single empty + // genesis block (one publisher used). Send a couple of txs from the default account so the + // sequencer publishes more blocks across rotated publishers. + for (let i = 0; i < 2; i++) { + await StatefulTestContract.deploy(wallet, defaultAccountAddress, 0, { + salt: Fr.random(), + deployer: defaultAccountAddress, + }).send({ from: defaultAccountAddress }); + } }); beforeEach(async () => { From 1cbd86519da1f96e58fdaa47d43a91b6ebe22c8f Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 10 Jun 2026 19:50:18 +0000 Subject: [PATCH 13/23] fix --- yarn-project/end-to-end/src/e2e_fees/account_init.test.ts | 1 + 1 file changed, 1 insertion(+) 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 b05bf4ce0edf..a8b3b341e0d4 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 @@ -151,6 +151,7 @@ describe('e2e_fees account_init', () => { const paymentMethod = new PublicFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings); const { receipt: tx } = await bobsDeployMethod.send({ from: NO_FROM, + skipClassPublication: false, skipInstancePublication: false, fee: { paymentMethod }, }); From a99388cb9be426b53885a80392cabed0fe7c3dd2 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 07:52:41 +0000 Subject: [PATCH 14/23] fixed initializerless --- yarn-project/accounts/src/schnorr/account_contract.ts | 2 +- yarn-project/accounts/src/schnorr/initializerless/index.ts | 4 ++++ yarn-project/accounts/src/schnorr/initializerless/lazy.ts | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/yarn-project/accounts/src/schnorr/account_contract.ts b/yarn-project/accounts/src/schnorr/account_contract.ts index 45fbf9658489..0bb1dd37c8b6 100644 --- a/yarn-project/accounts/src/schnorr/account_contract.ts +++ b/yarn-project/accounts/src/schnorr/account_contract.ts @@ -18,7 +18,7 @@ export abstract class SchnorrBaseAccountContract extends DefaultAccountContract super(); } - async getInitializationFunctionAndArgs() { + async getInitializationFunctionAndArgs(): Promise<{ constructorName: string; constructorArgs: any[] } | undefined> { const signingPublicKey = await new Schnorr().computePublicKey(this.signingPrivateKey); return { constructorName: 'constructor', constructorArgs: [signingPublicKey.x, signingPublicKey.y] }; } diff --git a/yarn-project/accounts/src/schnorr/initializerless/index.ts b/yarn-project/accounts/src/schnorr/initializerless/index.ts index 11a92100f324..209178b2bee5 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/index.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/index.ts @@ -31,6 +31,10 @@ export class SchnorrInitializerlessAccountContract extends SchnorrBaseAccountCon super(signingPrivateKey); } + override getInitializationFunctionAndArgs() { + return Promise.resolve(undefined); + } + override getContractArtifact(): Promise { return Promise.resolve(SchnorrInitializerlessAccountContractArtifact); } diff --git a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts index 61b49716c0da..c75e12ecd2b6 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts @@ -39,6 +39,10 @@ export class SchnorrInitializerlessAccountContract extends SchnorrBaseAccountCon super(signingPrivateKey); } + override getInitializationFunctionAndArgs() { + return Promise.resolve(undefined); + } + override getContractArtifact(): Promise { return getSchnorrInitializerlessAccountContractArtifact(); } From 2a56a5a26beaa35ea76ff70eb6a9f5a6225d1299 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 08:15:47 +0000 Subject: [PATCH 15/23] jsdoc --- yarn-project/accounts/src/schnorr/account_contract.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/yarn-project/accounts/src/schnorr/account_contract.ts b/yarn-project/accounts/src/schnorr/account_contract.ts index 0bb1dd37c8b6..5e000096f80a 100644 --- a/yarn-project/accounts/src/schnorr/account_contract.ts +++ b/yarn-project/accounts/src/schnorr/account_contract.ts @@ -18,7 +18,15 @@ export abstract class SchnorrBaseAccountContract extends DefaultAccountContract super(); } - async getInitializationFunctionAndArgs(): Promise<{ constructorName: string; constructorArgs: any[] } | undefined> { + async getInitializationFunctionAndArgs(): Promise< + | { + /** The name of the function used to initialize the contract */ + constructorName: string; + /** The args to the function used to initialize the contract */ + constructorArgs: any[]; + } + | undefined + > { const signingPublicKey = await new Schnorr().computePublicKey(this.signingPrivateKey); return { constructorName: 'constructor', constructorArgs: [signingPublicKey.x, signingPublicKey.y] }; } From 5077e8042bff0be97c49a8d2a59119f2c74d7ca5 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 08:36:18 +0000 Subject: [PATCH 16/23] fixes --- yarn-project/accounts/src/schnorr/initializerless/index.ts | 6 +++--- yarn-project/accounts/src/testing/configuration.ts | 6 ++---- yarn-project/accounts/src/testing/index.ts | 5 ++--- yarn-project/accounts/src/testing/lazy.ts | 5 ++--- yarn-project/end-to-end/src/e2e_bot.test.ts | 2 -- yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts | 3 +-- .../src/e2e_nested_contract/nested_contract_test.ts | 1 - yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts | 1 - yarn-project/end-to-end/src/fixtures/setup.ts | 3 +-- 9 files changed, 11 insertions(+), 21 deletions(-) diff --git a/yarn-project/accounts/src/schnorr/initializerless/index.ts b/yarn-project/accounts/src/schnorr/initializerless/index.ts index 209178b2bee5..6ca018c28249 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/index.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/index.ts @@ -7,6 +7,7 @@ import { getAccountContractAddress } from '@aztec/aztec.js/account'; import type { AztecAddress } from '@aztec/aztec.js/addresses'; import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; +import { Schnorr } from '@aztec/foundation/crypto/schnorr'; import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { ContractArtifact } from '@aztec/stdlib/abi'; @@ -53,8 +54,7 @@ export async function getSchnorrInitializerlessAccountContractAddress( ): Promise { const signingKey = signingPrivateKey ?? deriveSigningKey(secret); const accountContract = new SchnorrInitializerlessAccountContract(signingKey); - // The initializerless address commits to the public keys via the immutables hash, threaded like salt. - const { constructorArgs } = (await accountContract.getInitializationFunctionAndArgs())!; - const immutablesHash = await poseidon2Hash(constructorArgs); + const signingPublicKey = await new Schnorr().computePublicKey(signingKey); + const immutablesHash = await poseidon2Hash([signingPublicKey.x, signingPublicKey.y]); return await getAccountContractAddress(accountContract, secret, salt, immutablesHash); } diff --git a/yarn-project/accounts/src/testing/configuration.ts b/yarn-project/accounts/src/testing/configuration.ts index 6d407e9b6809..0255e9861b4e 100644 --- a/yarn-project/accounts/src/testing/configuration.ts +++ b/yarn-project/accounts/src/testing/configuration.ts @@ -18,8 +18,7 @@ export const INITIAL_TEST_SIGNING_KEYS = INITIAL_TEST_ENCRYPTION_KEYS; export const INITIAL_TEST_ACCOUNT_SALTS = [Fr.ZERO, Fr.ZERO, Fr.ZERO]; /** - * The schnorr account contract variant a test account uses. Determines both how its address is derived and - * which account type is created when prefunding it. Defaults to `schnorr_initializerless` when omitted. + * The schnorr account contract variant a test account uses. */ export type InitialAccountType = 'schnorr' | 'schnorr_initializerless'; @@ -44,8 +43,7 @@ export interface InitialAccountData { */ address: AztecAddress; /** - * Account contract variant to create when prefunding this account. The address above is derived from it. - * Omitted for special account contracts (e.g. hardcoded-key test accounts) that callers create themselves. + * Account contract variant. */ type?: InitialAccountType; } diff --git a/yarn-project/accounts/src/testing/index.ts b/yarn-project/accounts/src/testing/index.ts index d5aa46dc09c0..d74871b74869 100644 --- a/yarn-project/accounts/src/testing/index.ts +++ b/yarn-project/accounts/src/testing/index.ts @@ -27,7 +27,7 @@ export { type InitialAccountType, } from './configuration.js'; -/** Derives the account contract address for the given type, so it matches the account that gets created. */ +/** Derives the account contract address for the given type */ function getTestAccountAddress(type: InitialAccountType, secret: Fr, salt: Fr, signingKey?: GrumpkinScalar) { return type === 'schnorr' ? getSchnorrAccountContractAddress(secret, salt, signingKey) @@ -54,8 +54,7 @@ export function getInitialTestAccountsData(): Promise { } /** - * Generate a fixed amount of random schnorr account contract instances of the given type (defaults to - * initializerless). The returned addresses are derived to match the type so they can be prefunded. + * Generate a fixed amount of random schnorr account contract instances of the given type */ export async function generateSchnorrAccounts( numberOfAccounts: number, diff --git a/yarn-project/accounts/src/testing/lazy.ts b/yarn-project/accounts/src/testing/lazy.ts index 05a33357ccc5..6884ac5c736b 100644 --- a/yarn-project/accounts/src/testing/lazy.ts +++ b/yarn-project/accounts/src/testing/lazy.ts @@ -20,7 +20,7 @@ import { export { INITIAL_TEST_ACCOUNT_SALTS, INITIAL_TEST_SECRET_KEYS } from './configuration.js'; -/** Derives the account contract address for the given type, so it matches the account that gets created. */ +/** Derives the account contract address for the given type */ function getTestAccountAddress(type: InitialAccountType, secret: Fr, salt: Fr, signingKey?: GrumpkinScalar) { return type === 'schnorr' ? getSchnorrAccountContractAddress(secret, salt, signingKey) @@ -47,8 +47,7 @@ export function getInitialTestAccountsData(): Promise { } /** - * Generate a fixed amount of random schnorr account contract instances of the given type (defaults to - * initializerless). The returned addresses are derived to match the type so they can be prefunded. + * Generate a fixed amount of random schnorr account contract instances of the given type */ export async function generateSchnorrAccounts( numberOfAccounts: number, 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 eeb77bcf5fc2..514746bea348 100644 --- a/yarn-project/end-to-end/src/e2e_bot.test.ts +++ b/yarn-project/end-to-end/src/e2e_bot.test.ts @@ -48,8 +48,6 @@ describe('e2e_bot', () => { config: { l1RpcUrls }, } = setupResult); wallet = await EmbeddedWallet.create(aztecNode, { ephemeral: true }); - // Initializerless test accounts need no deployment tx; creating registers it and the genesis funding - // at its address makes it immediately usable. await wallet.createSchnorrInitializerlessAccount(botAccount.secret, botAccount.salt, botAccount.signingKey); }); diff --git a/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts b/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts index b307e1171c31..8d3e65140dad 100644 --- a/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts +++ b/yarn-project/end-to-end/src/e2e_circuit_recorder.test.ts @@ -18,8 +18,7 @@ describe('Circuit Recorder', () => { process.env.CIRCUIT_RECORD_DIR = RECORD_DIR; // setup creates an initializerless account, which has no deployment tx. Deploying a contract from - // it exercises the account entrypoint (a user circuit) and the private kernels (protocol circuits), - // which is what this test inspects below — without a pointless account deployment. + // it exercises the account entrypoint (a user circuit) and the private kernels (protocol circuits) const { teardown, wallet, diff --git a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts index 457015188120..fb586da297fe 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract/nested_contract_test.ts @@ -26,7 +26,6 @@ export class NestedContractTest { async setup(opts: Partial = {}) { this.logger.info('Setting up fresh subsystems'); - // setup creates `numberOfAccounts` initializerless accounts, available on the context. this.context = await setup(this.numberOfAccounts, { ...opts, fundSponsoredFPC: true, diff --git a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts index 0c9c15cab34b..83242518d906 100644 --- a/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/fixtures/e2e_prover_test.ts @@ -123,7 +123,6 @@ export class FullProverTest { startProverNode: true, coinbase: this.coinbase, fundSponsoredFPC: true, - // Fund 2 accounts that we register (initializerless) in both the main and the proven-PXE wallets below. additionallyFundedAccounts: await generateSchnorrAccounts(2), l1ContractsArgs: { realVerifier: this.realProofs }, }); diff --git a/yarn-project/end-to-end/src/fixtures/setup.ts b/yarn-project/end-to-end/src/fixtures/setup.ts index f732a96416d9..6ee3942fccc0 100644 --- a/yarn-project/end-to-end/src/fixtures/setup.ts +++ b/yarn-project/end-to-end/src/fixtures/setup.ts @@ -158,7 +158,6 @@ export type SetupOptions = { /** * Extra accounts to fund at genesis beyond the `numberOfAccounts` initializerless accounts that setup * creates. Setup funds these but does NOT create or deploy them — the test creates/deploys them itself - * (e.g. as a regular deployable account, or registered in a second PXE). Exposed on the context. */ additionallyFundedAccounts?: InitialAccountData[]; /** An initial set of validators */ @@ -404,7 +403,7 @@ export async function setup( } // The accounts setup creates itself: `numberOfAccounts` initializerless accounts, generated here and - // funded at genesis so they are immediately usable (initializerless accounts need no deployment tx). + // funded at genesis so they are immediately usable. const defaultAccounts = await generateSchnorrAccounts(numberOfAccounts); // Extra accounts the test wants funded at genesis but will create/deploy itself. const additionallyFundedAccounts = opts.additionallyFundedAccounts ?? []; From 06c040ac92a297e41cd0cd626224b279b1c35bb5 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 09:36:03 +0000 Subject: [PATCH 17/23] fixes --- .../accounts/src/defaults/account_contract.ts | 9 +++++++ .../accounts/src/schnorr/account_contract.ts | 15 ++++++++++-- .../src/schnorr/initializerless/index.ts | 9 ++++--- .../src/schnorr/initializerless/lazy.ts | 4 +--- .../aztec.js/src/account/account_contract.ts | 9 ++++++- .../aztec.js/src/wallet/account_manager.ts | 2 +- yarn-project/cli-wallet/src/utils/wallet.ts | 24 +++++++------------ .../end-to-end/src/test-wallet/test_wallet.ts | 19 +++++++-------- 8 files changed, 55 insertions(+), 36 deletions(-) diff --git a/yarn-project/accounts/src/defaults/account_contract.ts b/yarn-project/accounts/src/defaults/account_contract.ts index c4c6a10b857b..f97928166d1e 100644 --- a/yarn-project/accounts/src/defaults/account_contract.ts +++ b/yarn-project/accounts/src/defaults/account_contract.ts @@ -1,5 +1,6 @@ import { type Account, type AccountContract, type AuthWitnessProvider, BaseAccount } from '@aztec/aztec.js/account'; import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account'; +import type { Fr } from '@aztec/foundation/curves/bn254'; import type { ContractArtifact } from '@aztec/stdlib/abi'; import type { CompleteAddress } from '@aztec/stdlib/contract'; @@ -22,6 +23,14 @@ export abstract class DefaultAccountContract implements AccountContract { constructor() {} + /** + * Accounts without immutables (the default) contribute to their address via an on-chain + * initializer instead. Account contracts that commit immutables into their address override this. + */ + getImmutablesHash(): Promise { + return Promise.resolve(undefined); + } + getAccount(completeAddress: CompleteAddress): Account { const authWitnessProvider = this.getAuthWitnessProvider(completeAddress); return new BaseAccount( diff --git a/yarn-project/accounts/src/schnorr/account_contract.ts b/yarn-project/accounts/src/schnorr/account_contract.ts index 5e000096f80a..105bc588a341 100644 --- a/yarn-project/accounts/src/schnorr/account_contract.ts +++ b/yarn-project/accounts/src/schnorr/account_contract.ts @@ -1,4 +1,5 @@ import type { AuthWitnessProvider } from '@aztec/aztec.js/account'; +import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Schnorr } from '@aztec/foundation/crypto/schnorr'; import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; @@ -14,10 +15,20 @@ import { DefaultAccountContract } from '../defaults/account_contract.js'; * can be implemented with or without lazy loading. */ export abstract class SchnorrBaseAccountContract extends DefaultAccountContract { - constructor(private signingPrivateKey: GrumpkinScalar) { + constructor(protected signingPrivateKey: GrumpkinScalar) { super(); } + /** The Grumpkin public key this account verifies signatures against. */ + getSigningPublicKey() { + return new Schnorr().computePublicKey(this.signingPrivateKey); + } + + override async getImmutablesHash(): Promise { + const signingPublicKey = await this.getSigningPublicKey(); + return poseidon2Hash([signingPublicKey.x, signingPublicKey.y]); + } + async getInitializationFunctionAndArgs(): Promise< | { /** The name of the function used to initialize the contract */ @@ -27,7 +38,7 @@ export abstract class SchnorrBaseAccountContract extends DefaultAccountContract } | undefined > { - const signingPublicKey = await new Schnorr().computePublicKey(this.signingPrivateKey); + const signingPublicKey = await this.getSigningPublicKey(); return { constructorName: 'constructor', constructorArgs: [signingPublicKey.x, signingPublicKey.y] }; } diff --git a/yarn-project/accounts/src/schnorr/initializerless/index.ts b/yarn-project/accounts/src/schnorr/initializerless/index.ts index 6ca018c28249..2a17a6230ccb 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/index.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/index.ts @@ -36,6 +36,11 @@ export class SchnorrInitializerlessAccountContract extends SchnorrBaseAccountCon return Promise.resolve(undefined); } + override async getImmutablesHash(): Promise { + const signingPublicKey = await this.getSigningPublicKey(); + return poseidon2Hash([signingPublicKey.x, signingPublicKey.y]); + } + override getContractArtifact(): Promise { return Promise.resolve(SchnorrInitializerlessAccountContractArtifact); } @@ -54,7 +59,5 @@ export async function getSchnorrInitializerlessAccountContractAddress( ): Promise { const signingKey = signingPrivateKey ?? deriveSigningKey(secret); const accountContract = new SchnorrInitializerlessAccountContract(signingKey); - const signingPublicKey = await new Schnorr().computePublicKey(signingKey); - const immutablesHash = await poseidon2Hash([signingPublicKey.x, signingPublicKey.y]); - return await getAccountContractAddress(accountContract, secret, salt, immutablesHash); + return await getAccountContractAddress(accountContract, secret, salt); } diff --git a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts index c75e12ecd2b6..18eb72422566 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts @@ -61,7 +61,5 @@ export async function getSchnorrInitializerlessAccountContractAddress( ): Promise { const signingKey = signingPrivateKey ?? deriveSigningKey(secret); const accountContract = new SchnorrInitializerlessAccountContract(signingKey); - const { constructorArgs } = (await accountContract.getInitializationFunctionAndArgs())!; - const immutablesHash = await poseidon2Hash(constructorArgs); - return await getAccountContractAddress(accountContract, secret, salt, immutablesHash); + return await getAccountContractAddress(accountContract, secret, salt); } diff --git a/yarn-project/aztec.js/src/account/account_contract.ts b/yarn-project/aztec.js/src/account/account_contract.ts index 44802e041240..f75c501ad8ba 100644 --- a/yarn-project/aztec.js/src/account/account_contract.ts +++ b/yarn-project/aztec.js/src/account/account_contract.ts @@ -30,6 +30,13 @@ export interface AccountContract { | undefined >; + /** + * The hash of this account's immutable instantiation params, committed into its address. Returns + * undefined for accounts that have no immutables (these are instead deployed via an on-chain + * initializer, which contributes to the address through its initialization hash). + */ + getImmutablesHash(): Promise; + /** * Returns the account implementation for this account contract given an instance at the provided address. * The account is responsible for assembling tx requests given requested function calls, and @@ -66,7 +73,7 @@ export async function getAccountContractAddress( constructorArgs, salt, publicKeys, - immutablesHash, + immutablesHash: immutablesHash ?? (await accountContract.getImmutablesHash()), }); return instance.address; } diff --git a/yarn-project/aztec.js/src/wallet/account_manager.ts b/yarn-project/aztec.js/src/wallet/account_manager.ts index 0cfa9a50eb1d..fca04c280532 100644 --- a/yarn-project/aztec.js/src/wallet/account_manager.ts +++ b/yarn-project/aztec.js/src/wallet/account_manager.ts @@ -62,7 +62,7 @@ export class AccountManager { salt, publicKeys, deployer: opts?.deployer, - immutablesHash: opts?.immutablesHash, + immutablesHash: opts?.immutablesHash ?? (await accountContract.getImmutablesHash()), }); return new AccountManager(wallet, secretKey, accountContract, instance); diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index 0e2eecdcf6dc..74c1d047f97a 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -15,7 +15,6 @@ import { AccountManager, type Aliased, type SimulateOptions } from '@aztec/aztec import { TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; import { DefaultEntrypoint } from '@aztec/entrypoints/default'; -import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { LogFn } from '@aztec/foundation/log'; import type { NotesFilter } from '@aztec/pxe/client/lazy'; @@ -151,16 +150,8 @@ export class CLIWallet extends BaseWallet { return account; } - private async createAccount( - secret: Fr, - salt: Fr, - contract: AccountContract, - isInitializerless = false, - ): Promise { - const init = isInitializerless ? await contract.getInitializationFunctionAndArgs() : undefined; - const immutablesHash = init ? await poseidon2Hash(init.constructorArgs) : undefined; - - const accountManager = await AccountManager.create(this, secret, contract, { salt, immutablesHash }); + private async createAccount(secret: Fr, salt: Fr, contract: AccountContract): Promise { + const accountManager = await AccountManager.create(this, secret, contract, { salt }); const instance = accountManager.getInstance(); const artifact = await contract.getContractArtifact(); @@ -168,12 +159,16 @@ export class CLIWallet extends BaseWallet { await this.registerContract(instance, artifact, secret); this.accountCache.set(accountManager.address.toString(), await accountManager.getAccount()); - if (init) { - const constructorAbi = artifact.functions.find(f => f.name === init.constructorName); + // Initializerless accounts have no deployment tx; their address commits to the signing public key + // (via the contract's immutablesHash, resolved by AccountManager.create) and the constructor's + // storage writes are materialized locally via a simulated "store" call here. + if (contract instanceof SchnorrInitializerlessAccountContract) { + const constructorAbi = artifact.functions.find(f => f.name === 'constructor'); if (!constructorAbi) { throw new Error('Could not create SchnorrInitializerlessAccount: constructor ABI not found'); } - const storeCall = new ContractFunctionInteraction(this, instance.address, constructorAbi, init.constructorArgs); + const { x, y } = await contract.getSigningPublicKey(); + const storeCall = new ContractFunctionInteraction(this, instance.address, constructorAbi, [x, y]); await storeCall.simulate({ from: instance.address }); } @@ -209,7 +204,6 @@ export class CLIWallet extends BaseWallet { secretKey, salt, new SchnorrInitializerlessAccountContract(deriveSigningKey(secretKey)), - true, ); break; } 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 8c22bf52aad6..f6b4389e34d4 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 @@ -19,7 +19,6 @@ import { AccountManager, type SendOptions } from '@aztec/aztec.js/wallet'; import { TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet'; import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account'; import { DefaultEntrypoint } from '@aztec/entrypoints/default'; -import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fq, Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { NotesFilter } from '@aztec/pxe/client/lazy'; @@ -236,14 +235,11 @@ export class TestWallet extends BaseWallet { const type = accountData?.type ?? 'schnorr'; const contract = accountData?.contract ?? new SchnorrAccountContract(GrumpkinScalar.random()); - // Initializerless accounts have no deployment tx: the address commits to the public keys (immutables) - // and the constructor's storage writes are materialized locally via a simulated "store" call below. + // Initializerless accounts have no deployment tx: the address commits to the signing public key + // (via the contract's immutablesHash, resolved by AccountManager.create) and the constructor's + // storage writes are materialized locally via a simulated "store" call below. // Mirrors EmbeddedWallet.createAccountInternal. - const isInitializerless = type === 'schnorr_initializerless'; - const init = isInitializerless ? await contract.getInitializationFunctionAndArgs() : undefined; - const immutablesHash = init ? await poseidon2Hash(init.constructorArgs) : undefined; - - const accountManager = await AccountManager.create(this, secret, contract, { salt, immutablesHash }); + const accountManager = await AccountManager.create(this, secret, contract, { salt }); const instance = accountManager.getInstance(); const artifact = await contract.getContractArtifact(); @@ -253,12 +249,13 @@ export class TestWallet extends BaseWallet { const address = accountManager.address.toString(); this.accounts.set(address, { account: await accountManager.getAccount(), type }); - if (init) { - const constructorAbi = artifact.functions.find(f => f.name === init.constructorName); + if (contract instanceof SchnorrInitializerlessAccountContract) { + const constructorAbi = artifact.functions.find(f => f.name === 'constructor'); if (!constructorAbi) { throw new Error('Could not create SchnorrInitializerlessAccount: constructor ABI not found'); } - const storeCall = new ContractFunctionInteraction(this, instance.address, constructorAbi, init.constructorArgs); + const { x, y } = await contract.getSigningPublicKey(); + const storeCall = new ContractFunctionInteraction(this, instance.address, constructorAbi, [x, y]); await storeCall.simulate({ from: instance.address }); } From c1c1ee1f1e4af5402a0e60bc6c936cb2db8613f6 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 09:42:56 +0000 Subject: [PATCH 18/23] fix --- yarn-project/accounts/src/schnorr/initializerless/index.ts | 1 - yarn-project/accounts/src/schnorr/initializerless/lazy.ts | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/yarn-project/accounts/src/schnorr/initializerless/index.ts b/yarn-project/accounts/src/schnorr/initializerless/index.ts index 2a17a6230ccb..2c1df1e3e24d 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/index.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/index.ts @@ -7,7 +7,6 @@ import { getAccountContractAddress } from '@aztec/aztec.js/account'; import type { AztecAddress } from '@aztec/aztec.js/addresses'; import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; -import { Schnorr } from '@aztec/foundation/crypto/schnorr'; import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { ContractArtifact } from '@aztec/stdlib/abi'; diff --git a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts index 18eb72422566..f8c8cbdfba91 100644 --- a/yarn-project/accounts/src/schnorr/initializerless/lazy.ts +++ b/yarn-project/accounts/src/schnorr/initializerless/lazy.ts @@ -43,6 +43,11 @@ export class SchnorrInitializerlessAccountContract extends SchnorrBaseAccountCon return Promise.resolve(undefined); } + override async getImmutablesHash(): Promise { + const signingPublicKey = await this.getSigningPublicKey(); + return poseidon2Hash([signingPublicKey.x, signingPublicKey.y]); + } + override getContractArtifact(): Promise { return getSchnorrInitializerlessAccountContractArtifact(); } From ca7461164ac5df7bc1891acf1a149ffe546d67f0 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 10:17:39 +0000 Subject: [PATCH 19/23] more fixes --- boxes/boxes/react/src/config.ts | 2 +- boxes/boxes/vite/src/config.ts | 12 ++++-------- .../accounts/src/schnorr/account_contract.ts | 6 ------ 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/boxes/boxes/react/src/config.ts b/boxes/boxes/react/src/config.ts index b0fcd398f1ee..89a2a9e7050d 100644 --- a/boxes/boxes/react/src/config.ts +++ b/boxes/boxes/react/src/config.ts @@ -21,7 +21,7 @@ export class PrivateEnv { ); } - await wallet.createSchnorrAccount(accountData.secret, accountData.salt, accountData.signingKey); + await wallet.createSchnorrInitializerlessAccount(accountData.secret, accountData.salt, accountData.signingKey); this.wallet = wallet; this.defaultAccountAddress = accountData.address; } diff --git a/boxes/boxes/vite/src/config.ts b/boxes/boxes/vite/src/config.ts index a5dfc2a4cdee..4b28c6dad8d4 100644 --- a/boxes/boxes/vite/src/config.ts +++ b/boxes/boxes/vite/src/config.ts @@ -1,4 +1,4 @@ -import { getInitialTestAccountsData } from "@aztec/accounts/testing"; +import { getInitialTestAccountsData } from '@aztec/accounts/testing'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { Wallet } from '@aztec/aztec.js/wallet'; import { EmbeddedWallet } from '@aztec/wallets/embedded'; @@ -10,22 +10,18 @@ export class PrivateEnv { constructor() {} async init() { - const nodeURL = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; + const nodeURL = process.env.AZTEC_NODE_URL ?? 'http://localhost:8080'; const wallet = await EmbeddedWallet.create(nodeURL); const [accountData] = await getInitialTestAccountsData(); if (!accountData) { console.error( - "Account not found. Please connect the app to a testing environment with deployed and funded test accounts.", + 'Account not found. Please connect the app to a testing environment with deployed and funded test accounts.', ); } - await wallet.createSchnorrAccount( - accountData.secret, - accountData.salt, - accountData.signingKey, - ); + await wallet.createSchnorrInitializerlessAccount(accountData.secret, accountData.salt, accountData.signingKey); this.wallet = wallet; this.defaultAccountAddress = accountData.address; } diff --git a/yarn-project/accounts/src/schnorr/account_contract.ts b/yarn-project/accounts/src/schnorr/account_contract.ts index 105bc588a341..8e7a770ade9c 100644 --- a/yarn-project/accounts/src/schnorr/account_contract.ts +++ b/yarn-project/accounts/src/schnorr/account_contract.ts @@ -1,5 +1,4 @@ import type { AuthWitnessProvider } from '@aztec/aztec.js/account'; -import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Schnorr } from '@aztec/foundation/crypto/schnorr'; import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; @@ -24,11 +23,6 @@ export abstract class SchnorrBaseAccountContract extends DefaultAccountContract return new Schnorr().computePublicKey(this.signingPrivateKey); } - override async getImmutablesHash(): Promise { - const signingPublicKey = await this.getSigningPublicKey(); - return poseidon2Hash([signingPublicKey.x, signingPublicKey.y]); - } - async getInitializationFunctionAndArgs(): Promise< | { /** The name of the function used to initialize the contract */ From c29ad8240deb2484eac2f35ca87f61e24b5cc8b1 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 11:11:57 +0000 Subject: [PATCH 20/23] migration notes --- .../docs/resources/migration_notes.md | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index 3410cc18587f..239deb8a891b 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -9,6 +9,26 @@ Aztec is in active development. Each version may introduce breaking changes that ## TBD +### [Aztec.js] Prefunded local network test accounts are now initializerless + +The genesis-funded test accounts in the local network (sandbox), returned by `getInitialTestAccountsData()`, are now initializerless Schnorr accounts (`schnorr_initializerless`). An initializerless account has no on-chain deployment transaction: its address commits to the signing public key (through `immutables_hash`) and its contract state is materialized locally in the PXE, so these accounts are usable right away. + +Because their address is derived differently from a regular Schnorr account, register them with `createSchnorrInitializerlessAccount` rather than `createSchnorrAccount`. + +**Migration:** + +```diff +const [alice] = await getInitialTestAccountsData(); +- await wallet.createSchnorrAccount(alice.secret, alice.salt); ++ await wallet.createSchnorrInitializerlessAccount(alice.secret, alice.salt); +``` + +### [Aztec.js] `AccountContract` interface adds `getImmutablesHash()` + +The `AccountContract` interface now declares `getImmutablesHash(): Promise`, which returns the hash of the account's immutable instantiation parameters committed into its address, or `undefined` if the feature is not used. `getAccountContractAddress()` and `AccountManager.create()` call it to derive the address when an `immutablesHash` is not passed explicitly, so the address of an initializerless account is now resolved from the contract itself. + +Account contracts that extend `DefaultAccountContract` inherit a default implementation that returns `undefined` and need no changes. + ### [Aztec.nr] `messages::message_delivery` module moved to `messages::delivery` The `message_delivery` module has been renamed to `delivery`. Update imports accordingly: @@ -100,7 +120,7 @@ After the `auth_registry`, `public_checks`, and `multi_call_entrypoint` demotion ### [Aztec.nr] `multi_call_entrypoint` demoted from protocol contract -`multi_call_entrypoint` is no longer a protocol contract; its address is derived from its artifact rather than hardcoded at `6`, and PXE no longer auto-registers it. It is now a standard contract that PXE *preloads*: both `createPXE` and `EmbeddedWallet` preload the standard MultiCallEntrypoint automatically (and `EmbeddedWallet` additionally preloads `AuthRegistry`). **If you use the standard PXE or `EmbeddedWallet`, no changes are needed** — multicall keeps working out of the box. +`multi_call_entrypoint` is no longer a protocol contract; its address is derived from its artifact rather than hardcoded at `6`, and PXE no longer auto-registers it. It is now a standard contract that PXE _preloads_: both `createPXE` and `EmbeddedWallet` preload the standard MultiCallEntrypoint automatically (and `EmbeddedWallet` additionally preloads `AuthRegistry`). **If you use the standard PXE or `EmbeddedWallet`, no changes are needed** — multicall keeps working out of the box. To preload a different set of standard contracts (for example to also preload `PublicChecks`, which is not preloaded by default), a wallet or app passes its own `preloadedContractsProvider` through the wallet's PXE options: @@ -121,7 +141,7 @@ const wallet = await EmbeddedWallet.create(node, { }); ``` -The provider *replaces* the default list (it is not additive), so include every standard contract you want available. +The provider _replaces_ the default list (it is not additive), so include every standard contract you want available. ### [Aztec.nr] `public_checks` demoted from protocol contract From a7087b8f29f8a706c5969bc17ec93f58586c43fa Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 11:28:27 +0000 Subject: [PATCH 21/23] word --- docs/docs-words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs-words.txt b/docs/docs-words.txt index 9b08f9f45c73..13553bce3d78 100644 --- a/docs/docs-words.txt +++ b/docs/docs-words.txt @@ -63,6 +63,7 @@ colour Colour CapsuleStore initializerless +immutables colums Commiting comsumer From c7a6ef58d06de97e0458a4b8b3c3c8ed0e811fc2 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 11:34:24 +0000 Subject: [PATCH 22/23] fix ha test --- yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts | 3 +++ 1 file changed, 3 insertions(+) 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 1e66b068aa7e..861060b5a3e1 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 @@ -172,6 +172,9 @@ describe('HA Full Setup', () => { startProverNode: true, // Disable validation on this node disableValidator: true, + // This node cannot build blocks (validation is disabled and the committee's HA nodes start later), + // so setup must not wait for a block past genesis. + advancePastGenesis: false, // Enable P2P for transaction gossip p2pEnabled: true, // Enable slashing for testing governance + slashing vote coordination From 31b820cf2d2441421b16807312250f58e3861d51 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 11 Jun 2026 11:49:32 +0000 Subject: [PATCH 23/23] spellcheck --- docs/docs-developers/docs/resources/migration_notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index 239deb8a891b..2da21d8aac75 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -11,7 +11,7 @@ Aztec is in active development. Each version may introduce breaking changes that ### [Aztec.js] Prefunded local network test accounts are now initializerless -The genesis-funded test accounts in the local network (sandbox), returned by `getInitialTestAccountsData()`, are now initializerless Schnorr accounts (`schnorr_initializerless`). An initializerless account has no on-chain deployment transaction: its address commits to the signing public key (through `immutables_hash`) and its contract state is materialized locally in the PXE, so these accounts are usable right away. +The genesis-funded test accounts in the local network (sandbox), returned by `getInitialTestAccountsData()`, are now initializerless Schnorr accounts (`schnorr_initializerless`). An initializerless account has no onchain deployment transaction: its address commits to the signing public key (through `immutables_hash`) and its contract state is materialized locally in the PXE, so these accounts are usable right away. Because their address is derived differently from a regular Schnorr account, register them with `createSchnorrInitializerlessAccount` rather than `createSchnorrAccount`.