From 0f24411e53321be926ce5e25ca3e2b762aa13014 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 13:24:27 +0200 Subject: [PATCH 1/8] fix --- yarn-project/wallets/src/embedded/embedded_wallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index e5cbef12e2c2..8db82820b671 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -234,7 +234,7 @@ export class EmbeddedWallet extends BaseWallet { : executionPayload; const chainInfo = await this.getChainInfo(); - const accountOverrides = await this.buildAccountOverrides(this.scopesFrom(from, opts.additionalScopes)); + const accountOverrides = await this.buildAccountOverrides(scopes ?? []); const overrides = new SimulationOverrides(accountOverrides); let txRequest: TxExecutionRequest; From 6930f4381d137241b13fe958226bbeed6ec5be44 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 12:14:02 +0000 Subject: [PATCH 2/8] consolidate --- yarn-project/cli-wallet/src/utils/wallet.ts | 3 ++- yarn-project/end-to-end/src/test-wallet/test_wallet.ts | 5 +++-- yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts | 6 ++---- yarn-project/wallets/src/embedded/embedded_wallet.ts | 7 ++++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index dc799905dff2..3f13b733b055 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -237,7 +237,8 @@ export class CLIWallet extends BaseWallet { executionPayload: ExecutionPayload, opts: SimulateViaEntrypointOptions, ): Promise { - const { from, feeOptions, scopes } = opts; + const { from, feeOptions, additionalScopes } = opts; + const scopes = this.scopesFrom(from, additionalScopes); const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); const finalExecutionPayload = feeExecutionPayload ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) 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 0dbd8d2b2d68..1e8c22f2c9d0 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 @@ -235,7 +235,8 @@ export class TestWallet extends BaseWallet { executionPayload: ExecutionPayload, opts: SimulateViaEntrypointOptions, ): Promise { - const { from, feeOptions, scopes, skipTxValidation, skipFeeEnforcement } = opts; + const { from, feeOptions, additionalScopes, skipTxValidation, skipFeeEnforcement } = opts; + const scopes = this.scopesFrom(from, additionalScopes); const skipKernels = this.simulationMode !== 'full'; const useOverride = this.simulationMode === 'kernelless-override'; @@ -248,7 +249,7 @@ export class TestWallet extends BaseWallet { let overrides: SimulationOverrides | undefined; let txRequest: TxExecutionRequest; if (useOverride) { - const accountOverrides = await this.buildAccountOverrides(this.scopesFrom(from, opts.additionalScopes)); + const accountOverrides = await this.buildAccountOverrides(scopes); overrides = new SimulationOverrides(accountOverrides); } diff --git a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts index 3008f3fd0b86..bf7422b9fb2f 100644 --- a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts +++ b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts @@ -87,8 +87,6 @@ export type SimulateViaEntrypointOptions = Pick< > & { /** Fee options for the entrypoint */ feeOptions: FeeOptions; - /** Scopes to use for the simulation */ - scopes: AztecAddress[]; }; /** Options for `completeFeeOptions`. */ @@ -319,7 +317,7 @@ export abstract class BaseWallet implements Wallet { simulatePublic: true, skipTxValidation: opts.skipTxValidation, skipFeeEnforcement: opts.skipFeeEnforcement, - scopes: opts.scopes, + scopes: this.scopesFrom(opts.from, opts.additionalScopes), }); const appCallOffset = await this.computeAppCallOffset(opts.from, opts.feeOptions); return TxSimulationResultWithAppOffset.fromResultAndOffset(result, appCallOffset); @@ -388,7 +386,7 @@ export abstract class BaseWallet implements Wallet { ? this.simulateViaEntrypoint(remainingPayload, { from: opts.from, feeOptions, - scopes: this.scopesFrom(opts.from, opts.additionalScopes), + additionalScopes: opts.additionalScopes, skipTxValidation: opts.skipTxValidation, skipFeeEnforcement: opts.skipFeeEnforcement ?? true, }) diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index 8db82820b671..3630b4039afb 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -130,7 +130,7 @@ export class EmbeddedWallet extends BaseWallet { const simulationResult = await this.simulateViaEntrypoint(executionPayload, { from: opts.from, feeOptions, - scopes: this.scopesFrom(opts.from, opts.additionalScopes), + additionalScopes: opts.additionalScopes, skipTxValidation: true, }); @@ -226,7 +226,8 @@ export class EmbeddedWallet extends BaseWallet { executionPayload: ExecutionPayload, opts: SimulateViaEntrypointOptions, ): Promise { - const { from, feeOptions, scopes, skipTxValidation, skipFeeEnforcement } = opts; + const { from, feeOptions, additionalScopes, skipTxValidation, skipFeeEnforcement } = opts; + const scopes = this.scopesFrom(from, additionalScopes); const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); const finalExecutionPayload = feeExecutionPayload @@ -234,7 +235,7 @@ export class EmbeddedWallet extends BaseWallet { : executionPayload; const chainInfo = await this.getChainInfo(); - const accountOverrides = await this.buildAccountOverrides(scopes ?? []); + const accountOverrides = await this.buildAccountOverrides(scopes); const overrides = new SimulationOverrides(accountOverrides); let txRequest: TxExecutionRequest; From 925104406353472c205fb056ee9fee22a824ca45 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 13:01:40 +0000 Subject: [PATCH 3/8] proper stubs --- noir-projects/noir-contracts/Nargo.toml | 3 +- .../Nargo.toml | 8 ++ .../src/main.nr | 81 ++++++++++++++++++ .../Nargo.toml | 8 ++ .../src/main.nr | 82 +++++++++++++++++++ .../accounts/scripts/copy-contracts.sh | 2 +- yarn-project/accounts/src/stub/index.ts | 63 +++++++++++--- yarn-project/accounts/src/stub/lazy.ts | 68 ++++++++++----- yarn-project/cli-wallet/src/utils/wallet.ts | 18 ++-- .../end-to-end/src/test-wallet/test_wallet.ts | 23 ++++-- .../proxied_contract_data_source.ts | 8 +- .../account-contract-providers/bundle.ts | 16 ++-- .../account-contract-providers/lazy.ts | 18 ++-- .../account-contract-providers/types.ts | 6 +- .../wallets/src/embedded/embedded_wallet.ts | 9 +- 15 files changed, 350 insertions(+), 63 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr create mode 100644 noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 0f876dd56034..4129c0e52f54 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -4,7 +4,8 @@ members = [ "contracts/account/ecdsa_r_account_contract", "contracts/account/schnorr_account_contract", "contracts/account/schnorr_hardcoded_account_contract", - "contracts/account/simulated_account_contract", + "contracts/account/simulated_schnorr_account_contract", + "contracts/account/simulated_ecdsa_account_contract", "contracts/app/amm_contract", "contracts/app/app_subscription_contract", "contracts/app/auth_contract", diff --git a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/Nargo.toml new file mode 100644 index 000000000000..71998b27abbb --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "simulated_ecdsa_account_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr new file mode 100644 index 000000000000..7f137f77f2fc --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr @@ -0,0 +1,81 @@ +use aztec::macros::aztec; + +// Stub account contract for ECDSA accounts (both secp256k1 and secp256r1) used during simulation. +// Matches the constructor signature of EcdsaKAccount / EcdsaRAccount so that deployment +// simulations using this stub as an override do not fail on selector lookup. +// See simulated_account_contract for the base stub without a constructor. +#[aztec] +pub contract SimulatedEcdsaAccount { + use aztec::{ + authwit::{account::AccountActions, auth::IS_VALID_SELECTOR, entrypoint::app::AppPayload}, + context::PrivateContext, + macros::functions::{allow_phase_change, external, initializer, view}, + messages::encoding::MESSAGE_CIPHERTEXT_LEN, + oracle::{notes::set_sender_for_tags, random::random}, + }; + + // Stub constructor matching the EcdsaKAccount / EcdsaRAccount constructor signature. + // Emits the same shape of side effects as the real constructor (one nullifier for + // SinglePrivateImmutable initialization, one note hash for the key note, and one private + // log tied to that note hash) so that gas estimation during kernelless deployment simulation + // produces accurate results. + #[external("private")] + #[initializer] + fn constructor(_signing_pub_key_x: [u8; 32], _signing_pub_key_y: [u8; 32]) { + // Safety: Random seeds are only used to produce dummy side effects that match the shape of + // the real EcdsaKAccount / EcdsaRAccount constructor. + let seed = unsafe { random() }; + + // Emit the initialization nullifier for the signing_public_key SinglePrivateImmutable. + self.context.push_nullifier(seed); + + // Emit the note hash for the signing key note. We capture the counter before the push + // because push_note_hash() internally calls next_counter(), which returns the current + // value and then increments it. + let note_hash_counter = self.context.side_effect_counter; + self.context.push_note_hash(seed + 1); + + // Emit a private log tied to the note hash, matching the length of a real note delivery + // log (MESSAGE_CIPHERTEXT_LEN fields). The signing key note is a SinglePrivateImmutable + // and is therefore never nullified, so in practice the log will never be squashed. We + // pass the note hash counter anyway for correctness. + let dummy_log: [Field; MESSAGE_CIPHERTEXT_LEN] = [seed + 2; MESSAGE_CIPHERTEXT_LEN]; + self.context.emit_raw_note_log_unsafe( + seed + 3, + dummy_log, + MESSAGE_CIPHERTEXT_LEN, + note_hash_counter, + ); + } + + // @dev: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts + #[external("private")] + #[allow_phase_change] + fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { + // Safety: The sender for tags is only used to compute unconstrained shared secrets for + // emitting logs. It is not used in any constrained logic, so it is safe to set here. + unsafe { set_sender_for_tags(self.address) }; + + 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 + } + + #[external("utility")] + unconstrained fn sync_state() { + assert( + false, + "BUG ALERT: sync_state on a simulated account contract should never be triggered.", + ); + } +} diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/Nargo.toml new file mode 100644 index 000000000000..f2ebb5cadf60 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "simulated_schnorr_account_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr new file mode 100644 index 000000000000..f5756ba957fd --- /dev/null +++ b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr @@ -0,0 +1,82 @@ +use aztec::macros::aztec; + +// Stub account contract for Schnorr accounts used during simulation. +// Matches the constructor signature of SchnorrAccount so that deployment +// simulations using this stub as an override do not fail on selector lookup. +// See simulated_account_contract for the base stub without a constructor. +#[aztec] +pub contract SimulatedSchnorrAccount { + use aztec::{ + authwit::{account::AccountActions, auth::IS_VALID_SELECTOR, entrypoint::app::AppPayload}, + context::PrivateContext, + macros::functions::{allow_phase_change, external, initializer, view}, + messages::encoding::MESSAGE_CIPHERTEXT_LEN, + oracle::{notes::set_sender_for_tags, random::random}, + }; + + // Stub constructor matching the SchnorrAccount constructor signature. + // Emits the same shape of side effects as the real constructor (one nullifier for + // SinglePrivateImmutable initialization, one note hash for the key note, and one private + // log tied to that note hash) so that gas estimation during kernelless deployment simulation + // produces accurate results. + #[external("private")] + #[initializer] + fn constructor(_signing_pub_key_x: Field, _signing_pub_key_y: Field) { + // Safety: Random seeds are only used to produce dummy side effects that match the shape of + // the real SchnorrAccount constructor. The values are never constrained or used for any + // security purpose. + let seed = unsafe { random() }; + + // Emit the initialization nullifier for the signing_public_key SinglePrivateImmutable. + self.context.push_nullifier(seed); + + // Emit the note hash for the signing key note. We capture the counter before the push + // because push_note_hash() internally calls next_counter(), which returns the current + // value and then increments it. + let note_hash_counter = self.context.side_effect_counter; + self.context.push_note_hash(seed + 1); + + // Emit a private log tied to the note hash, matching the length of a real note delivery + // log (MESSAGE_CIPHERTEXT_LEN fields). The signing key note is a SinglePrivateImmutable + // and is therefore never nullified, so in practice the log will never be squashed. We + // pass the note hash counter anyway for correctness. + let dummy_log: [Field; MESSAGE_CIPHERTEXT_LEN] = [seed + 2; MESSAGE_CIPHERTEXT_LEN]; + self.context.emit_raw_note_log_unsafe( + seed + 3, + dummy_log, + MESSAGE_CIPHERTEXT_LEN, + note_hash_counter, + ); + } + + // @dev: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts + #[external("private")] + #[allow_phase_change] + fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { + // Safety: The sender for tags is only used to compute unconstrained shared secrets for + // emitting logs. It is not used in any constrained logic, so it is safe to set here. + unsafe { set_sender_for_tags(self.address) }; + + 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 + } + + #[external("utility")] + unconstrained fn sync_state() { + assert( + false, + "BUG ALERT: sync_state on a simulated account contract should never be triggered.", + ); + } +} diff --git a/yarn-project/accounts/scripts/copy-contracts.sh b/yarn-project/accounts/scripts/copy-contracts.sh index 16dfcd9b3325..c8babc835659 100755 --- a/yarn-project/accounts/scripts/copy-contracts.sh +++ b/yarn-project/accounts/scripts/copy-contracts.sh @@ -2,7 +2,7 @@ set -euo pipefail mkdir -p ./artifacts -contracts=(schnorr_account_contract-SchnorrAccount ecdsa_k_account_contract-EcdsaKAccount ecdsa_r_account_contract-EcdsaRAccount simulated_account_contract-SimulatedAccount ) +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 ) decl=$(cat < { + return Promise.resolve(StubSchnorrAccountContractArtifact); } +} +/** + * Stub account contract for ECDSA accounts (secp256k1 and secp256r1). + * Eagerly loads the contract artifact. + */ +export class StubEcdsaAccountContract extends StubBaseAccountContract { override getContractArtifact(): Promise { - return Promise.resolve(StubAccountContractArtifact); + return Promise.resolve(StubEcdsaAccountContractArtifact); } } +/** + * Creates a Schnorr stub account that impersonates the one with the provided originalAddress. + */ +export function createStubSchnorrAccount(originalAddress: CompleteAddress) { + const accountContract = new StubSchnorrAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} + +/** + * Creates an ECDSA stub account that impersonates the one with the provided originalAddress. + */ +export function createStubEcdsaAccount(originalAddress: CompleteAddress) { + const accountContract = new StubEcdsaAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} + /** * Creates a stub account that impersonates the one with the provided originalAddress. - * @param originalAddress - The address of the account to stub - * @returns A stub account that can be used for kernelless simulations + * The artifact must be either {@link StubSchnorrAccountContractArtifact} or + * {@link StubEcdsaAccountContractArtifact}; it determines which stub contract class is instantiated. */ -export function createStubAccount(originalAddress: CompleteAddress) { - const accountContract = new StubAccountContract(); +export function createStubAccount(originalAddress: CompleteAddress, artifact: ContractArtifact) { + const accountContract = + artifact === StubSchnorrAccountContractArtifact ? new StubSchnorrAccountContract() : new StubEcdsaAccountContract(); const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); return new BaseAccount( new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), diff --git a/yarn-project/accounts/src/stub/lazy.ts b/yarn-project/accounts/src/stub/lazy.ts index 4332560b0c81..98d53741624f 100644 --- a/yarn-project/accounts/src/stub/lazy.ts +++ b/yarn-project/accounts/src/stub/lazy.ts @@ -7,41 +7,71 @@ import { loadContractArtifact } from '@aztec/stdlib/abi'; import { StubBaseAccountContract } from './account_contract.js'; /** - * Lazily loads the contract artifact - * @returns The contract artifact for the Stub account contract + * Lazily loads the Schnorr stub contract artifact (browser-compatible). */ -export async function getStubAccountContractArtifact() { +export async function getStubSchnorrAccountContractArtifact() { // 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: StubAccountContractJson } = await import('../../artifacts/SimulatedAccount.json'); - return loadContractArtifact(StubAccountContractJson); + const { default: json } = await import('../../artifacts/SimulatedSchnorrAccount.json'); + return loadContractArtifact(json); } /** - * Account contract that authenticates transactions using Stub signatures - * verified against a Grumpkin public key stored in an immutable encrypted note. - * Lazily loads the contract artifact + * Lazily loads the ECDSA stub contract artifact (browser-compatible). */ -export class StubAccountContract extends StubBaseAccountContract { - constructor() { - super(); +export async function getStubEcdsaAccountContractArtifact() { + // Cannot assert this import as it's incompatible with bundlers like vite + // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 + const { default: json } = await import('../../artifacts/SimulatedEcdsaAccount.json'); + return loadContractArtifact(json); +} + +export class StubSchnorrAccountContract extends StubBaseAccountContract { + override getContractArtifact(): Promise { + return getStubSchnorrAccountContractArtifact(); } +} +export class StubEcdsaAccountContract extends StubBaseAccountContract { override getContractArtifact(): Promise { - return getStubAccountContractArtifact(); + return getStubEcdsaAccountContractArtifact(); } } +/** + * Creates a Schnorr stub account that impersonates the one with the provided originalAddress. + */ +export function createStubSchnorrAccount(originalAddress: CompleteAddress) { + const accountContract = new StubSchnorrAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} + +/** + * Creates an ECDSA stub account that impersonates the one with the provided originalAddress. + */ +export function createStubEcdsaAccount(originalAddress: CompleteAddress) { + const accountContract = new StubEcdsaAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} + /** * Creates a stub account that impersonates the one with the provided originalAddress. - * @param originalAddress - The address of the account to stub - * @returns A stub account that can be used for kernelless simulations + * The artifact must be either the Schnorr or ECDSA stub artifact returned by the + * corresponding getter above; it determines which stub contract class is instantiated. */ -export function createStubAccount(originalAddress: CompleteAddress) { - const accountContract = new StubAccountContract(); +export function createStubAccount(originalAddress: CompleteAddress, artifact: ContractArtifact) { + const accountContract = + artifact.name === 'SimulatedSchnorrAccount' ? new StubSchnorrAccountContract() : new StubEcdsaAccountContract(); const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); return new BaseAccount( new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index 3f13b733b055..41fe2b4abf3e 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -1,6 +1,10 @@ import { EcdsaRAccountContract, EcdsaRSSHAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { StubAccountContractArtifact, createStubAccount } from '@aztec/accounts/stub'; +import { + StubEcdsaAccountContractArtifact, + StubSchnorrAccountContractArtifact, + createStubAccount, +} from '@aztec/accounts/stub'; import { getIdentities } from '@aztec/accounts/utils'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import { @@ -200,15 +204,13 @@ export class CLIWallet extends BaseWallet { if (!contractInstance) { throw new Error(`No contract instance found for address: ${originalAddress.address}`); } - const stubAccount = createStubAccount(originalAddress); - const instance = await getContractInstanceFromInstantiationParams(StubAccountContractArtifact, { + const { type } = await this.db!.retrieveAccount(address); + const artifact = type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact; + const stubAccount = createStubAccount(originalAddress, artifact); + const instance = await getContractInstanceFromInstantiationParams(artifact, { salt: Fr.random(), }); - return { - account: stubAccount, - instance, - artifact: StubAccountContractArtifact, - }; + return { account: stubAccount, instance, artifact }; } override async simulateTx( 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 1e8c22f2c9d0..e04d311dcfe5 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,10 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { StubAccountContractArtifact, createStubAccount } from '@aztec/accounts/stub'; +import { + StubEcdsaAccountContractArtifact, + StubSchnorrAccountContractArtifact, + createStubAccount, +} from '@aztec/accounts/stub'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import { type CallIntent, @@ -131,13 +135,14 @@ export class TestWallet extends BaseWallet { ); } - const stubInstance = await getContractInstanceFromInstantiationParams(StubAccountContractArtifact, { + const stubArtifact = this.accountStubArtifacts.get(address.toString()) ?? StubSchnorrAccountContractArtifact; + const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random(), }); contracts[address.toString()] = { instance: stubInstance, - artifact: StubAccountContractArtifact, + artifact: stubArtifact, }; } @@ -145,6 +150,7 @@ export class TestWallet extends BaseWallet { } protected accounts: Map = new Map(); + private accountStubArtifacts: Map = new Map(); /** * Controls how the test wallet simulates transactions: @@ -188,7 +194,13 @@ export class TestWallet extends BaseWallet { await this.registerContract(instance, artifact, secret); - this.accounts.set(accountManager.address.toString(), await accountManager.getAccount()); + const address = accountManager.address.toString(); + this.accounts.set(address, await accountManager.getAccount()); + const isEcdsa = contract instanceof EcdsaKAccountContract || contract instanceof EcdsaRAccountContract; + this.accountStubArtifacts.set( + address, + isEcdsa ? StubEcdsaAccountContractArtifact : StubSchnorrAccountContractArtifact, + ); return accountManager; } @@ -261,7 +273,8 @@ export class TestWallet extends BaseWallet { if (useOverride) { const originalAccount = await this.getAccountFromAddress(from); const completeAddress = originalAccount.getCompleteAddress(); - fromAccount = createStubAccount(completeAddress); + const stubArtifact = this.accountStubArtifacts.get(from.toString()) ?? StubSchnorrAccountContractArtifact; + fromAccount = createStubAccount(completeAddress, stubArtifact); } else { fromAccount = await this.getAccountFromAddress(from); } diff --git a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts index d825ef65c730..a18690c88dce 100644 --- a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts +++ b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts @@ -47,6 +47,9 @@ export class ProxiedContractStoreFactory { return fn; } } + throw new Error( + `Function with selector ${selector} not found in stub artifact for overridden contract at ${contractAddress}. The stub does not implement this function.`, + ); } else { return target.getFunctionArtifact(contractAddress, selector); } @@ -64,6 +67,9 @@ export class ProxiedContractStoreFactory { return fn; } } + throw new Error( + `Function with selector ${selector} not found in stub artifact for overridden contract at ${contractAddress}. The stub does not implement this function.`, + ); } else { return target.getFunctionArtifactWithDebugMetadata(contractAddress, selector); } @@ -78,6 +84,6 @@ export class ProxiedContractStoreFactory { } } }, - }); + }) satisfies ContractStore; } } 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 8e4a49f12e9b..93912123d600 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts @@ -1,12 +1,17 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { StubAccountContractArtifact, createStubAccount } from '@aztec/accounts/stub'; +import { + StubEcdsaAccountContractArtifact, + StubSchnorrAccountContractArtifact, + createStubAccount, +} from '@aztec/accounts/stub'; import type { Account, AccountContract } from '@aztec/aztec.js/account'; import type { Fq } from '@aztec/foundation/curves/bn254'; import { getCanonicalMultiCallEntrypoint } from '@aztec/protocol-contracts/multi-call-entrypoint'; import type { ContractArtifact } from '@aztec/stdlib/abi'; import type { CompleteAddress, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; +import type { AccountType } from '../wallet_db.js'; import type { AccountContractsProvider } from './types.js'; /** @@ -26,12 +31,13 @@ export class BundleAccountContractsProvider implements AccountContractsProvider return Promise.resolve(new EcdsaKAccountContract(signingKey)); } - getStubAccountContractArtifact(): Promise { - return Promise.resolve(StubAccountContractArtifact); + getStubAccountContractArtifact(type: AccountType): Promise { + return Promise.resolve(type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact); } - createStubAccount(address: CompleteAddress): Promise { - return Promise.resolve(createStubAccount(address)); + createStubAccount(address: CompleteAddress, type: AccountType): Promise { + const artifact = type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact; + return Promise.resolve(createStubAccount(address, artifact)); } getMulticallContract(): Promise<{ instance: ContractInstanceWithAddress; artifact: ContractArtifact }> { 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 1da707e921f6..7742df401711 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts @@ -4,6 +4,7 @@ import { getCanonicalMultiCallEntrypoint } from '@aztec/protocol-contracts/multi import type { ContractArtifact } from '@aztec/stdlib/abi'; import type { CompleteAddress, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; +import type { AccountType } from '../wallet_db.js'; import type { AccountContractsProvider } from './types.js'; /** @@ -26,14 +27,19 @@ export class LazyAccountContractsProvider implements AccountContractsProvider { return new EcdsaKAccountContract(signingKey); } - async getStubAccountContractArtifact(): Promise { - const { getStubAccountContractArtifact } = await import('@aztec/accounts/stub/lazy'); - return getStubAccountContractArtifact(); + async getStubAccountContractArtifact(type: AccountType): Promise { + const { getStubSchnorrAccountContractArtifact, getStubEcdsaAccountContractArtifact } = await import( + '@aztec/accounts/stub/lazy' + ); + return type === 'schnorr' ? getStubSchnorrAccountContractArtifact() : getStubEcdsaAccountContractArtifact(); } - async createStubAccount(address: CompleteAddress): Promise { - const { createStubAccount } = await import('@aztec/accounts/stub/lazy'); - return createStubAccount(address); + async createStubAccount(address: CompleteAddress, type: AccountType): Promise { + const { createStubAccount, getStubSchnorrAccountContractArtifact, getStubEcdsaAccountContractArtifact } = + await import('@aztec/accounts/stub/lazy'); + const artifact = + type === 'schnorr' ? await getStubSchnorrAccountContractArtifact() : await getStubEcdsaAccountContractArtifact(); + return createStubAccount(address, artifact); } getMulticallContract(): Promise<{ instance: ContractInstanceWithAddress; artifact: ContractArtifact }> { 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 a99b5c504268..62441a21ce1d 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/types.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/types.ts @@ -3,6 +3,8 @@ import type { Fq } from '@aztec/foundation/curves/bn254'; import type { ContractArtifact } from '@aztec/stdlib/abi'; import type { CompleteAddress, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; +import type { AccountType } from '../wallet_db.js'; + /** * Provides account contract implementations and stub accounts for the EmbeddedWallet. * Two implementations exist: @@ -13,7 +15,7 @@ export interface AccountContractsProvider { getSchnorrAccountContract(signingKey: Fq): Promise; getEcdsaRAccountContract(signingKey: Buffer): Promise; getEcdsaKAccountContract(signingKey: Buffer): Promise; - getStubAccountContractArtifact(): Promise; + getStubAccountContractArtifact(type: AccountType): Promise; getMulticallContract(): Promise<{ instance: ContractInstanceWithAddress; artifact: ContractArtifact }>; - createStubAccount(address: CompleteAddress): Promise; + createStubAccount(address: CompleteAddress, type: AccountType): Promise; } diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index 3630b4039afb..ffa3d3d764e0 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -184,17 +184,19 @@ export class EmbeddedWallet extends BaseWallet { /** * Builds contract overrides for all provided addresses by replacing their account contracts with stub implementations. + * Uses a type-specific stub artifact so that the stub's constructor selector matches the real account's constructor. */ protected async buildAccountOverrides(addresses: AztecAddress[]): Promise { const accounts = await this.getAccounts(); const contracts: ContractOverrides = {}; - const stubArtifact = await this.accountContracts.getStubAccountContractArtifact(); - const filtered = accounts.filter(acc => addresses.some(addr => addr.equals(acc.item))); for (const account of filtered) { const address = account.item; + const { type } = await this.walletDB.retrieveAccount(address); + const stubArtifact = await this.accountContracts.getStubAccountContractArtifact(type); + const originalAccount = await this.getAccountFromAddress(address); const completeAddress = originalAccount.getCompleteAddress(); const contractInstance = await this.pxe.getContractInstance(completeAddress.address); @@ -243,9 +245,10 @@ export class EmbeddedWallet extends BaseWallet { const entrypoint = new DefaultEntrypoint(); txRequest = await entrypoint.createTxExecutionRequest(finalExecutionPayload, feeOptions.gasSettings, chainInfo); } else { + const { type } = await this.walletDB.retrieveAccount(from); const originalAccount = await this.getAccountFromAddress(from); const completeAddress = originalAccount.getCompleteAddress(); - const account = await this.accountContracts.createStubAccount(completeAddress); + const account = await this.accountContracts.createStubAccount(completeAddress, type); const executionOptions: DefaultAccountEntrypointOptions = { txNonce: Fr.random(), cancellable: this.cancellableTransactions, From a58b6320c0b4ece8eabf84ce7d16cbdf87b78791 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 13:50:33 +0000 Subject: [PATCH 4/8] better approach --- yarn-project/accounts/package.json | 6 +- yarn-project/accounts/src/stub/ecdsa/index.ts | 28 +++++++ yarn-project/accounts/src/stub/ecdsa/lazy.ts | 34 ++++++++ yarn-project/accounts/src/stub/index.ts | 79 ------------------ yarn-project/accounts/src/stub/lazy.ts | 81 ------------------- .../accounts/src/stub/schnorr/index.ts | 30 +++++++ .../accounts/src/stub/schnorr/lazy.ts | 34 ++++++++ yarn-project/cli-wallet/src/utils/wallet.ts | 16 ++-- .../end-to-end/src/test-wallet/test_wallet.ts | 49 ++++++----- .../account-contract-providers/bundle.ts | 10 +-- .../account-contract-providers/lazy.ts | 23 +++--- 11 files changed, 181 insertions(+), 209 deletions(-) create mode 100644 yarn-project/accounts/src/stub/ecdsa/index.ts create mode 100644 yarn-project/accounts/src/stub/ecdsa/lazy.ts delete mode 100644 yarn-project/accounts/src/stub/index.ts delete mode 100644 yarn-project/accounts/src/stub/lazy.ts create mode 100644 yarn-project/accounts/src/stub/schnorr/index.ts create mode 100644 yarn-project/accounts/src/stub/schnorr/lazy.ts diff --git a/yarn-project/accounts/package.json b/yarn-project/accounts/package.json index 01f6ed385e54..1cfdc55fb2c8 100644 --- a/yarn-project/accounts/package.json +++ b/yarn-project/accounts/package.json @@ -10,8 +10,10 @@ "./ecdsa/lazy": "./dest/ecdsa/lazy.js", "./schnorr": "./dest/schnorr/index.js", "./schnorr/lazy": "./dest/schnorr/lazy.js", - "./stub": "./dest/stub/index.js", - "./stub/lazy": "./dest/stub/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", "./testing": "./dest/testing/index.js", "./testing/lazy": "./dest/testing/lazy.js", "./copy-cat": "./dest/copy_cat/index.js", diff --git a/yarn-project/accounts/src/stub/ecdsa/index.ts b/yarn-project/accounts/src/stub/ecdsa/index.ts new file mode 100644 index 000000000000..4b714103d1b8 --- /dev/null +++ b/yarn-project/accounts/src/stub/ecdsa/index.ts @@ -0,0 +1,28 @@ +import { BaseAccount } from '@aztec/aztec.js/account'; +import type { CompleteAddress } from '@aztec/aztec.js/addresses'; +import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account'; +import { loadContractArtifact } from '@aztec/stdlib/abi'; +import type { NoirCompiledContract } from '@aztec/stdlib/noir'; + +import SimulatedEcdsaAccountJson from '../../../artifacts/SimulatedEcdsaAccount.json' with { type: 'json' }; +import { StubBaseAccountContract } from '../account_contract.js'; + +export const StubEcdsaAccountContractArtifact = loadContractArtifact(SimulatedEcdsaAccountJson as NoirCompiledContract); + +/** Stub account contract for ECDSA accounts (secp256k1 and secp256r1). Eagerly loads the contract artifact. */ +export class StubEcdsaAccountContract extends StubBaseAccountContract { + override getContractArtifact() { + return Promise.resolve(StubEcdsaAccountContractArtifact); + } +} + +/** Creates an ECDSA stub account that impersonates the one with the provided address. */ +export function createStubEcdsaAccount(originalAddress: CompleteAddress) { + const accountContract = new StubEcdsaAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} diff --git a/yarn-project/accounts/src/stub/ecdsa/lazy.ts b/yarn-project/accounts/src/stub/ecdsa/lazy.ts new file mode 100644 index 000000000000..5042c4df43b2 --- /dev/null +++ b/yarn-project/accounts/src/stub/ecdsa/lazy.ts @@ -0,0 +1,34 @@ +import { BaseAccount } from '@aztec/aztec.js/account'; +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'; + +/** + * Lazily loads the ECDSA stub contract artifact (browser-compatible). + */ +export async function getStubEcdsaAccountContractArtifact() { + // Cannot assert this import as it's incompatible with bundlers like vite + // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 + const { default: json } = await import('../../../artifacts/SimulatedEcdsaAccount.json'); + return loadContractArtifact(json); +} + +/** Stub account contract for ECDSA accounts (secp256k1 and secp256r1). Lazily loads the contract artifact. */ +export class StubEcdsaAccountContract extends StubBaseAccountContract { + override getContractArtifact() { + return getStubEcdsaAccountContractArtifact(); + } +} + +/** Creates an ECDSA stub account that impersonates the one with the provided address. */ +export function createStubEcdsaAccount(originalAddress: CompleteAddress) { + const accountContract = new StubEcdsaAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} diff --git a/yarn-project/accounts/src/stub/index.ts b/yarn-project/accounts/src/stub/index.ts deleted file mode 100644 index 70487316dc99..000000000000 --- a/yarn-project/accounts/src/stub/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { BaseAccount } from '@aztec/aztec.js/account'; -import type { CompleteAddress } from '@aztec/aztec.js/addresses'; -import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account'; -import type { ContractArtifact } from '@aztec/stdlib/abi'; -import { loadContractArtifact } from '@aztec/stdlib/abi'; -import type { NoirCompiledContract } from '@aztec/stdlib/noir'; - -import SimulatedEcdsaAccountContract from '../../artifacts/SimulatedEcdsaAccount.json' with { type: 'json' }; -import SimulatedSchnorrAccountContract from '../../artifacts/SimulatedSchnorrAccount.json' with { type: 'json' }; -import { StubBaseAccountContract } from './account_contract.js'; - -export const StubSchnorrAccountContractArtifact = loadContractArtifact( - SimulatedSchnorrAccountContract as NoirCompiledContract, -); -export const StubEcdsaAccountContractArtifact = loadContractArtifact( - SimulatedEcdsaAccountContract as NoirCompiledContract, -); - -/** - * Stub account contract for Schnorr accounts. - * Eagerly loads the contract artifact. - */ -export class StubSchnorrAccountContract extends StubBaseAccountContract { - override getContractArtifact(): Promise { - return Promise.resolve(StubSchnorrAccountContractArtifact); - } -} - -/** - * Stub account contract for ECDSA accounts (secp256k1 and secp256r1). - * Eagerly loads the contract artifact. - */ -export class StubEcdsaAccountContract extends StubBaseAccountContract { - override getContractArtifact(): Promise { - return Promise.resolve(StubEcdsaAccountContractArtifact); - } -} - -/** - * Creates a Schnorr stub account that impersonates the one with the provided originalAddress. - */ -export function createStubSchnorrAccount(originalAddress: CompleteAddress) { - const accountContract = new StubSchnorrAccountContract(); - const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); - return new BaseAccount( - new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), - authWitnessProvider, - originalAddress, - ); -} - -/** - * Creates an ECDSA stub account that impersonates the one with the provided originalAddress. - */ -export function createStubEcdsaAccount(originalAddress: CompleteAddress) { - const accountContract = new StubEcdsaAccountContract(); - const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); - return new BaseAccount( - new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), - authWitnessProvider, - originalAddress, - ); -} - -/** - * Creates a stub account that impersonates the one with the provided originalAddress. - * The artifact must be either {@link StubSchnorrAccountContractArtifact} or - * {@link StubEcdsaAccountContractArtifact}; it determines which stub contract class is instantiated. - */ -export function createStubAccount(originalAddress: CompleteAddress, artifact: ContractArtifact) { - const accountContract = - artifact === StubSchnorrAccountContractArtifact ? new StubSchnorrAccountContract() : new StubEcdsaAccountContract(); - const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); - return new BaseAccount( - new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), - authWitnessProvider, - originalAddress, - ); -} diff --git a/yarn-project/accounts/src/stub/lazy.ts b/yarn-project/accounts/src/stub/lazy.ts deleted file mode 100644 index 98d53741624f..000000000000 --- a/yarn-project/accounts/src/stub/lazy.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { BaseAccount } from '@aztec/aztec.js/account'; -import type { CompleteAddress } from '@aztec/aztec.js/addresses'; -import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account'; -import type { ContractArtifact } from '@aztec/stdlib/abi'; -import { loadContractArtifact } from '@aztec/stdlib/abi'; - -import { StubBaseAccountContract } from './account_contract.js'; - -/** - * Lazily loads the Schnorr stub contract artifact (browser-compatible). - */ -export async function getStubSchnorrAccountContractArtifact() { - // Cannot assert this import as it's incompatible with bundlers like vite - // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - const { default: json } = await import('../../artifacts/SimulatedSchnorrAccount.json'); - return loadContractArtifact(json); -} - -/** - * Lazily loads the ECDSA stub contract artifact (browser-compatible). - */ -export async function getStubEcdsaAccountContractArtifact() { - // Cannot assert this import as it's incompatible with bundlers like vite - // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 - const { default: json } = await import('../../artifacts/SimulatedEcdsaAccount.json'); - return loadContractArtifact(json); -} - -export class StubSchnorrAccountContract extends StubBaseAccountContract { - override getContractArtifact(): Promise { - return getStubSchnorrAccountContractArtifact(); - } -} - -export class StubEcdsaAccountContract extends StubBaseAccountContract { - override getContractArtifact(): Promise { - return getStubEcdsaAccountContractArtifact(); - } -} - -/** - * Creates a Schnorr stub account that impersonates the one with the provided originalAddress. - */ -export function createStubSchnorrAccount(originalAddress: CompleteAddress) { - const accountContract = new StubSchnorrAccountContract(); - const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); - return new BaseAccount( - new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), - authWitnessProvider, - originalAddress, - ); -} - -/** - * Creates an ECDSA stub account that impersonates the one with the provided originalAddress. - */ -export function createStubEcdsaAccount(originalAddress: CompleteAddress) { - const accountContract = new StubEcdsaAccountContract(); - const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); - return new BaseAccount( - new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), - authWitnessProvider, - originalAddress, - ); -} - -/** - * Creates a stub account that impersonates the one with the provided originalAddress. - * The artifact must be either the Schnorr or ECDSA stub artifact returned by the - * corresponding getter above; it determines which stub contract class is instantiated. - */ -export function createStubAccount(originalAddress: CompleteAddress, artifact: ContractArtifact) { - const accountContract = - artifact.name === 'SimulatedSchnorrAccount' ? new StubSchnorrAccountContract() : new StubEcdsaAccountContract(); - const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); - return new BaseAccount( - new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), - authWitnessProvider, - originalAddress, - ); -} diff --git a/yarn-project/accounts/src/stub/schnorr/index.ts b/yarn-project/accounts/src/stub/schnorr/index.ts new file mode 100644 index 000000000000..640343cd0c5b --- /dev/null +++ b/yarn-project/accounts/src/stub/schnorr/index.ts @@ -0,0 +1,30 @@ +import { BaseAccount } from '@aztec/aztec.js/account'; +import type { CompleteAddress } from '@aztec/aztec.js/addresses'; +import { DefaultAccountEntrypoint } from '@aztec/entrypoints/account'; +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'; + +export const StubSchnorrAccountContractArtifact = loadContractArtifact( + SimulatedSchnorrAccountJson as NoirCompiledContract, +); + +/** Stub account contract for Schnorr accounts. Eagerly loads the contract artifact. */ +export class StubSchnorrAccountContract extends StubBaseAccountContract { + override getContractArtifact() { + return Promise.resolve(StubSchnorrAccountContractArtifact); + } +} + +/** Creates a Schnorr stub account that impersonates the one with the provided address. */ +export function createStubSchnorrAccount(originalAddress: CompleteAddress) { + const accountContract = new StubSchnorrAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} diff --git a/yarn-project/accounts/src/stub/schnorr/lazy.ts b/yarn-project/accounts/src/stub/schnorr/lazy.ts new file mode 100644 index 000000000000..413a42f3692b --- /dev/null +++ b/yarn-project/accounts/src/stub/schnorr/lazy.ts @@ -0,0 +1,34 @@ +import { BaseAccount } from '@aztec/aztec.js/account'; +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'; + +/** + * Lazily loads the Schnorr stub contract artifact (browser-compatible). + */ +export async function getStubSchnorrAccountContractArtifact() { + // Cannot assert this import as it's incompatible with bundlers like vite + // https://github.com/vitejs/vite/issues/19095#issuecomment-2566074352 + const { default: json } = await import('../../../artifacts/SimulatedSchnorrAccount.json'); + return loadContractArtifact(json); +} + +/** Stub account contract for Schnorr accounts. Lazily loads the contract artifact. */ +export class StubSchnorrAccountContract extends StubBaseAccountContract { + override getContractArtifact() { + return getStubSchnorrAccountContractArtifact(); + } +} + +/** Creates a Schnorr stub account that impersonates the one with the provided address. */ +export function createStubSchnorrAccount(originalAddress: CompleteAddress) { + const accountContract = new StubSchnorrAccountContract(); + const authWitnessProvider = accountContract.getAuthWitnessProvider(originalAddress); + return new BaseAccount( + new DefaultAccountEntrypoint(originalAddress.address, authWitnessProvider), + authWitnessProvider, + originalAddress, + ); +} diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index 41fe2b4abf3e..44329c0ed830 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -1,10 +1,7 @@ import { EcdsaRAccountContract, EcdsaRSSHAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { - StubEcdsaAccountContractArtifact, - StubSchnorrAccountContractArtifact, - createStubAccount, -} from '@aztec/accounts/stub'; +import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/stub/ecdsa'; +import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr'; import { getIdentities } from '@aztec/accounts/utils'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; import { @@ -205,11 +202,10 @@ export class CLIWallet extends BaseWallet { throw new Error(`No contract instance found for address: ${originalAddress.address}`); } const { type } = await this.db!.retrieveAccount(address); - const artifact = type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact; - const stubAccount = createStubAccount(originalAddress, artifact); - const instance = await getContractInstanceFromInstantiationParams(artifact, { - salt: Fr.random(), - }); + const isSchnorr = type === 'schnorr'; + const artifact = isSchnorr ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact; + const stubAccount = isSchnorr ? createStubSchnorrAccount(originalAddress) : createStubEcdsaAccount(originalAddress); + const instance = await getContractInstanceFromInstantiationParams(artifact, { salt: Fr.random() }); return { account: stubAccount, instance, artifact }; } 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 e04d311dcfe5..60595e98789a 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,11 +1,9 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { - StubEcdsaAccountContractArtifact, - StubSchnorrAccountContractArtifact, - createStubAccount, -} from '@aztec/accounts/stub'; +import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/stub/ecdsa'; +import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr'; import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account'; +import type { CompleteAddress } from '@aztec/aztec.js/addresses'; import { type CallIntent, type ContractFunctionInteractionCallIntent, @@ -135,7 +133,7 @@ export class TestWallet extends BaseWallet { ); } - const stubArtifact = this.accountStubArtifacts.get(address.toString()) ?? StubSchnorrAccountContractArtifact; + const stubArtifact = this.getStubArtifactFor(address); const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random(), }); @@ -149,8 +147,22 @@ export class TestWallet extends BaseWallet { return contracts; } - protected accounts: Map = new Map(); - private accountStubArtifacts: Map = new Map(); + protected accounts: Map = new Map(); + + private isEcdsaAccount(address: AztecAddress) { + const entry = this.accounts.get(address.toString()); + return entry?.contract instanceof EcdsaKAccountContract || entry?.contract instanceof EcdsaRAccountContract; + } + + private getStubArtifactFor(address: AztecAddress) { + return this.isEcdsaAccount(address) ? StubEcdsaAccountContractArtifact : StubSchnorrAccountContractArtifact; + } + + private getStubAccountFor(address: AztecAddress, completeAddress: CompleteAddress) { + return this.isEcdsaAccount(address) + ? createStubEcdsaAccount(completeAddress) + : createStubSchnorrAccount(completeAddress); + } /** * Controls how the test wallet simulates transactions: @@ -169,17 +181,19 @@ export class TestWallet extends BaseWallet { } protected getAccountFromAddress(address: AztecAddress): Promise { - const account = this.accounts.get(address?.toString() ?? ''); + const entry = this.accounts.get(address?.toString() ?? ''); - if (!account) { + if (!entry) { throw new Error(`Account not found in wallet for address: ${address}`); } - return Promise.resolve(account); + return Promise.resolve(entry.account); } getAccounts() { - return Promise.resolve(Array.from(this.accounts.values()).map(acc => ({ alias: '', item: acc.getAddress() }))); + return Promise.resolve( + Array.from(this.accounts.values()).map(entry => ({ alias: '', item: entry.account.getAddress() })), + ); } async createAccount(accountData?: AccountData): Promise { @@ -195,12 +209,7 @@ export class TestWallet extends BaseWallet { await this.registerContract(instance, artifact, secret); const address = accountManager.address.toString(); - this.accounts.set(address, await accountManager.getAccount()); - const isEcdsa = contract instanceof EcdsaKAccountContract || contract instanceof EcdsaRAccountContract; - this.accountStubArtifacts.set( - address, - isEcdsa ? StubEcdsaAccountContractArtifact : StubSchnorrAccountContractArtifact, - ); + this.accounts.set(address, { account: await accountManager.getAccount(), contract }); return accountManager; } @@ -272,9 +281,7 @@ export class TestWallet extends BaseWallet { let fromAccount: Account; if (useOverride) { const originalAccount = await this.getAccountFromAddress(from); - const completeAddress = originalAccount.getCompleteAddress(); - const stubArtifact = this.accountStubArtifacts.get(from.toString()) ?? StubSchnorrAccountContractArtifact; - fromAccount = createStubAccount(completeAddress, stubArtifact); + fromAccount = this.getStubAccountFor(from, originalAccount.getCompleteAddress()); } else { fromAccount = await this.getAccountFromAddress(from); } 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 93912123d600..c854dd077e97 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/bundle.ts @@ -1,10 +1,7 @@ import { EcdsaKAccountContract, EcdsaRAccountContract } from '@aztec/accounts/ecdsa'; import { SchnorrAccountContract } from '@aztec/accounts/schnorr'; -import { - StubEcdsaAccountContractArtifact, - StubSchnorrAccountContractArtifact, - createStubAccount, -} from '@aztec/accounts/stub'; +import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/stub/ecdsa'; +import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr'; import type { Account, AccountContract } from '@aztec/aztec.js/account'; import type { Fq } from '@aztec/foundation/curves/bn254'; import { getCanonicalMultiCallEntrypoint } from '@aztec/protocol-contracts/multi-call-entrypoint'; @@ -36,8 +33,7 @@ export class BundleAccountContractsProvider implements AccountContractsProvider } createStubAccount(address: CompleteAddress, type: AccountType): Promise { - const artifact = type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact; - return Promise.resolve(createStubAccount(address, artifact)); + return Promise.resolve(type === 'schnorr' ? createStubSchnorrAccount(address) : createStubEcdsaAccount(address)); } getMulticallContract(): Promise<{ instance: ContractInstanceWithAddress; artifact: ContractArtifact }> { 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 7742df401711..501212d7f7b5 100644 --- a/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts +++ b/yarn-project/wallets/src/embedded/account-contract-providers/lazy.ts @@ -28,18 +28,23 @@ export class LazyAccountContractsProvider implements AccountContractsProvider { } async getStubAccountContractArtifact(type: AccountType): Promise { - const { getStubSchnorrAccountContractArtifact, getStubEcdsaAccountContractArtifact } = await import( - '@aztec/accounts/stub/lazy' - ); - return type === 'schnorr' ? getStubSchnorrAccountContractArtifact() : getStubEcdsaAccountContractArtifact(); + if (type === 'schnorr') { + const { getStubSchnorrAccountContractArtifact } = await import('@aztec/accounts/stub/schnorr/lazy'); + return getStubSchnorrAccountContractArtifact(); + } else { + const { getStubEcdsaAccountContractArtifact } = await import('@aztec/accounts/stub/ecdsa/lazy'); + return getStubEcdsaAccountContractArtifact(); + } } async createStubAccount(address: CompleteAddress, type: AccountType): Promise { - const { createStubAccount, getStubSchnorrAccountContractArtifact, getStubEcdsaAccountContractArtifact } = - await import('@aztec/accounts/stub/lazy'); - const artifact = - type === 'schnorr' ? await getStubSchnorrAccountContractArtifact() : await getStubEcdsaAccountContractArtifact(); - return createStubAccount(address, artifact); + if (type === 'schnorr') { + const { createStubSchnorrAccount } = await import('@aztec/accounts/stub/schnorr/lazy'); + return createStubSchnorrAccount(address); + } else { + const { createStubEcdsaAccount } = await import('@aztec/accounts/stub/ecdsa/lazy'); + return createStubEcdsaAccount(address); + } } getMulticallContract(): Promise<{ instance: ContractInstanceWithAddress; artifact: ContractArtifact }> { From 1f5fe22dafe2ddd22b607dc592381b8bed3e85be Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 13:53:31 +0000 Subject: [PATCH 5/8] fix build --- yarn-project/end-to-end/src/e2e_account_contracts.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 f501ec18c8ef..182492731adb 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 @@ -31,7 +31,8 @@ export class TestWalletInternals extends TestWallet { } replaceAccountAt(account: Account, address: AztecAddress) { - this.accounts.set(address.toString(), account); + const existing = this.accounts.get(address.toString()); + this.accounts.set(address.toString(), { account, contract: existing!.contract }); } } From 5666af1b525584b650dbc11d5ff0ea98d9d405bf Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 15:08:32 +0000 Subject: [PATCH 6/8] working --- .../simulated_account_contract/Nargo.toml | 8 --- .../simulated_account_contract/src/main.nr | 52 ------------------- .../src/main.nr | 19 ++++--- .../src/main.nr | 16 +++--- .../end-to-end/src/test-wallet/test_wallet.ts | 4 ++ .../proxied_contract_data_source.ts | 1 + .../wallets/src/embedded/embedded_wallet.ts | 2 + 7 files changed, 23 insertions(+), 79 deletions(-) delete mode 100644 noir-projects/noir-contracts/contracts/account/simulated_account_contract/Nargo.toml delete mode 100644 noir-projects/noir-contracts/contracts/account/simulated_account_contract/src/main.nr diff --git a/noir-projects/noir-contracts/contracts/account/simulated_account_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/account/simulated_account_contract/Nargo.toml deleted file mode 100644 index 3a2a16d624f3..000000000000 --- a/noir-projects/noir-contracts/contracts/account/simulated_account_contract/Nargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "simulated_account_contract" -authors = [""] -compiler_version = ">=0.25.0" -type = "contract" - -[dependencies] -aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/account/simulated_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_account_contract/src/main.nr deleted file mode 100644 index f6d70cde52ed..000000000000 --- a/noir-projects/noir-contracts/contracts/account/simulated_account_contract/src/main.nr +++ /dev/null @@ -1,52 +0,0 @@ -use aztec::macros::aztec; - -// Account contract that does not perform any authentication, typically used to override a *real* account contract -// in a simulation. This avoids the need of generating real signatures during simulation. -// It will also consider all authwits sent to it as valid and emit the hash in an offchain effect -// for later verification. In order to properly mimic our account contract it imports the PublickKeyNote -// struct so the #[aztec] macro can generate a `sync_state` implementation that can process the *real* -// public key of the overridden contract -#[aztec] -pub contract SimulatedAccount { - use aztec::{ - authwit::{account::AccountActions, auth::IS_VALID_SELECTOR, entrypoint::app::AppPayload}, - context::PrivateContext, - macros::functions::{allow_phase_change, external, view}, - oracle::notes::set_sender_for_tags, - }; - - // @dev: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts (specifically `getEntrypointAbi()`) - #[external("private")] - #[allow_phase_change] - fn entrypoint(app_payload: AppPayload, fee_payment_method: u8, cancellable: bool) { - // Safety: The sender for tags is only used to compute unconstrained shared secrets for emitting logs. - // Since this value is only used for unconstrained tagging and not for any constrained logic, - // it is safe to set from a constrained context. - unsafe { set_sender_for_tags(self.address) }; - - 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 - } - - // This contract override exists solely to enable simulation without requiring users to sign anything. Therefore, - // this function should never be called - this contract should never be used to sync the state of the original - // contract! Doing so could result in an undefined behavior. - #[external("utility")] - unconstrained fn sync_state() { - assert( - false, - "BUG ALERT: sync_state on a simulated account contract should never be triggered.", - ); - } -} diff --git a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr index 7f137f77f2fc..b27a1d6f5a0e 100644 --- a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr @@ -9,30 +9,29 @@ pub contract SimulatedEcdsaAccount { use aztec::{ authwit::{account::AccountActions, auth::IS_VALID_SELECTOR, entrypoint::app::AppPayload}, context::PrivateContext, - macros::functions::{allow_phase_change, external, initializer, view}, + macros::functions::{allow_phase_change, external, view}, messages::encoding::MESSAGE_CIPHERTEXT_LEN, oracle::{notes::set_sender_for_tags, random::random}, }; // Stub constructor matching the EcdsaKAccount / EcdsaRAccount constructor signature. + // Does NOT use #[initializer] so that the macro does not inject + // assert_initialization_matches_address_preimage_private, which would fail during kernelless + // simulation because the stub instance has a different initialization hash than the real account. // Emits the same shape of side effects as the real constructor (one nullifier for // SinglePrivateImmutable initialization, one note hash for the key note, and one private - // log tied to that note hash) so that gas estimation during kernelless deployment simulation - // produces accurate results. + // log tied to that note hash) so that gas estimation produces accurate results. #[external("private")] - #[initializer] fn constructor(_signing_pub_key_x: [u8; 32], _signing_pub_key_y: [u8; 32]) { // Safety: Random seeds are only used to produce dummy side effects that match the shape of - // the real EcdsaKAccount / EcdsaRAccount constructor. + // the real EcdsaKAccount / EcdsaRAccount constructor. The values are never constrained or + // used for any security purpose. let seed = unsafe { random() }; // Emit the initialization nullifier for the signing_public_key SinglePrivateImmutable. self.context.push_nullifier(seed); - // Emit the note hash for the signing key note. We capture the counter before the push - // because push_note_hash() internally calls next_counter(), which returns the current - // value and then increments it. - let note_hash_counter = self.context.side_effect_counter; + // Emit the note hash for the signing key note. self.context.push_note_hash(seed + 1); // Emit a private log tied to the note hash, matching the length of a real note delivery @@ -44,7 +43,7 @@ pub contract SimulatedEcdsaAccount { seed + 3, dummy_log, MESSAGE_CIPHERTEXT_LEN, - note_hash_counter, + self.context.side_effect_counter, ); } diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr index f5756ba957fd..4ac523076a6e 100644 --- a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr @@ -9,18 +9,19 @@ pub contract SimulatedSchnorrAccount { use aztec::{ authwit::{account::AccountActions, auth::IS_VALID_SELECTOR, entrypoint::app::AppPayload}, context::PrivateContext, - macros::functions::{allow_phase_change, external, initializer, view}, + macros::functions::{allow_phase_change, external, view}, messages::encoding::MESSAGE_CIPHERTEXT_LEN, oracle::{notes::set_sender_for_tags, random::random}, }; // Stub constructor matching the SchnorrAccount constructor signature. + // Does NOT use #[initializer] so that the macro does not inject + // assert_initialization_matches_address_preimage_private, which would fail during kernelless + // simulation because the stub instance has a different initialization hash than the real account. // Emits the same shape of side effects as the real constructor (one nullifier for // SinglePrivateImmutable initialization, one note hash for the key note, and one private - // log tied to that note hash) so that gas estimation during kernelless deployment simulation - // produces accurate results. + // log tied to that note hash) so that gas estimation produces accurate results. #[external("private")] - #[initializer] fn constructor(_signing_pub_key_x: Field, _signing_pub_key_y: Field) { // Safety: Random seeds are only used to produce dummy side effects that match the shape of // the real SchnorrAccount constructor. The values are never constrained or used for any @@ -30,10 +31,7 @@ pub contract SimulatedSchnorrAccount { // Emit the initialization nullifier for the signing_public_key SinglePrivateImmutable. self.context.push_nullifier(seed); - // Emit the note hash for the signing key note. We capture the counter before the push - // because push_note_hash() internally calls next_counter(), which returns the current - // value and then increments it. - let note_hash_counter = self.context.side_effect_counter; + // Emit the note hash for the signing key note. self.context.push_note_hash(seed + 1); // Emit a private log tied to the note hash, matching the length of a real note delivery @@ -45,7 +43,7 @@ pub contract SimulatedSchnorrAccount { seed + 3, dummy_log, MESSAGE_CIPHERTEXT_LEN, - note_hash_counter, + self.context.side_effect_counter, ); } 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 60595e98789a..77a24fd55c83 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 @@ -134,8 +134,12 @@ export class TestWallet extends BaseWallet { } const stubArtifact = this.getStubArtifactFor(address); + const stubConstructorArgs = this.isEcdsaAccount(address) + ? [Buffer.alloc(32), Buffer.alloc(32)] + : [Fr.ZERO, Fr.ZERO]; const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random(), + constructorArgs: stubConstructorArgs, }); contracts[address.toString()] = { diff --git a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts index a18690c88dce..f3edf78718f8 100644 --- a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts +++ b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts @@ -29,6 +29,7 @@ export class ProxiedContractStoreFactory { } instance.currentContractClassId = realInstance.currentContractClassId; instance.originalContractClassId = realInstance.originalContractClassId; + instance.initializationHash = realInstance.initializationHash; return instance; } else { return target.getContractInstance(address); diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index ffa3d3d764e0..10914e0915f7 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -206,8 +206,10 @@ export class EmbeddedWallet extends BaseWallet { ); } + const stubConstructorArgs = type === 'schnorr' ? [Fr.ZERO, Fr.ZERO] : [Buffer.alloc(32), Buffer.alloc(32)]; const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random(), + constructorArgs: stubConstructorArgs, }); contracts[address.toString()] = { From f25f205b0a35dfa24f532e73272350d59e947eb4 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 15:21:42 +0000 Subject: [PATCH 7/8] cleaner --- .../src/e2e_account_contracts.test.ts | 2 +- .../end-to-end/src/test-wallet/test_wallet.ts | 46 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) 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 182492731adb..d48af0ff7fe3 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 @@ -32,7 +32,7 @@ export class TestWalletInternals extends TestWallet { replaceAccountAt(account: Account, address: AztecAddress) { const existing = this.accounts.get(address.toString()); - this.accounts.set(address.toString(), { account, contract: existing!.contract }); + this.accounts.set(address.toString(), { account, type: existing!.type }); } } 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 77a24fd55c83..811ae8717c1b 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 @@ -38,6 +38,7 @@ import { } from '@aztec/stdlib/tx'; import { ExecutionPayload, mergeExecutionPayloads } from '@aztec/stdlib/tx'; import { BaseWallet, type SimulateViaEntrypointOptions } from '@aztec/wallet-sdk/base-wallet'; +import type { AccountType } from '@aztec/wallets/embedded'; import { AztecNodeProxy, ProvenTx } from './utils.js'; @@ -47,6 +48,7 @@ import { AztecNodeProxy, ProvenTx } from './utils.js'; export interface AccountData { secret: Fr; salt: Fr; + type: AccountType; contract: AccountContract; } @@ -87,30 +89,25 @@ export class TestWallet extends BaseWallet { createSchnorrAccount(secret: Fr, salt: Fr, signingKey?: Fq): Promise { signingKey = signingKey ?? deriveSigningKey(secret); - const accountData = { - secret, - salt, - contract: new SchnorrAccountContract(signingKey), - }; - return this.createAccount(accountData); + return this.createAccount({ secret, salt, type: 'schnorr', contract: new SchnorrAccountContract(signingKey) }); } createECDSARAccount(secret: Fr, salt: Fr, signingKey: Buffer): Promise { - const accountData = { + return this.createAccount({ secret, salt, + type: 'ecdsasecp256r1', contract: new EcdsaRAccountContract(signingKey), - }; - return this.createAccount(accountData); + }); } createECDSAKAccount(secret: Fr, salt: Fr, signingKey: Buffer): Promise { - const accountData = { + return this.createAccount({ secret, salt, + type: 'ecdsasecp256k1', contract: new EcdsaKAccountContract(signingKey), - }; - return this.createAccount(accountData); + }); } /** @@ -134,9 +131,8 @@ export class TestWallet extends BaseWallet { } const stubArtifact = this.getStubArtifactFor(address); - const stubConstructorArgs = this.isEcdsaAccount(address) - ? [Buffer.alloc(32), Buffer.alloc(32)] - : [Fr.ZERO, Fr.ZERO]; + const stubConstructorArgs = + this.getTypeFor(address) === 'schnorr' ? [Fr.ZERO, Fr.ZERO] : [Buffer.alloc(32), Buffer.alloc(32)]; const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random(), constructorArgs: stubConstructorArgs, @@ -151,21 +147,22 @@ export class TestWallet extends BaseWallet { return contracts; } - protected accounts: Map = new Map(); + protected accounts: Map = new Map(); - private isEcdsaAccount(address: AztecAddress) { - const entry = this.accounts.get(address.toString()); - return entry?.contract instanceof EcdsaKAccountContract || entry?.contract instanceof EcdsaRAccountContract; + private getTypeFor(address: AztecAddress): AccountType { + return this.accounts.get(address.toString())?.type ?? 'schnorr'; } private getStubArtifactFor(address: AztecAddress) { - return this.isEcdsaAccount(address) ? StubEcdsaAccountContractArtifact : StubSchnorrAccountContractArtifact; + return this.getTypeFor(address) === 'schnorr' + ? StubSchnorrAccountContractArtifact + : StubEcdsaAccountContractArtifact; } private getStubAccountFor(address: AztecAddress, completeAddress: CompleteAddress) { - return this.isEcdsaAccount(address) - ? createStubEcdsaAccount(completeAddress) - : createStubSchnorrAccount(completeAddress); + return this.getTypeFor(address) === 'schnorr' + ? createStubSchnorrAccount(completeAddress) + : createStubEcdsaAccount(completeAddress); } /** @@ -203,6 +200,7 @@ export class TestWallet extends BaseWallet { async createAccount(accountData?: AccountData): Promise { const secret = accountData?.secret ?? Fr.random(); const salt = accountData?.salt ?? Fr.random(); + const type = accountData?.type ?? 'schnorr'; const contract = accountData?.contract ?? new SchnorrAccountContract(GrumpkinScalar.random()); const accountManager = await AccountManager.create(this, secret, contract, salt); @@ -213,7 +211,7 @@ export class TestWallet extends BaseWallet { await this.registerContract(instance, artifact, secret); const address = accountManager.address.toString(); - this.accounts.set(address, { account: await accountManager.getAccount(), contract }); + this.accounts.set(address, { account: await accountManager.getAccount(), type }); return accountManager; } From abe5accdde97490d8e79640bad9a89c5047852fa Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 10 Apr 2026 15:43:41 +0000 Subject: [PATCH 8/8] type --- yarn-project/end-to-end/src/test-wallet/test_wallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 811ae8717c1b..8541c349f028 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 @@ -48,7 +48,7 @@ import { AztecNodeProxy, ProvenTx } from './utils.js'; export interface AccountData { secret: Fr; salt: Fr; - type: AccountType; + type?: AccountType; contract: AccountContract; }