Skip to content

Commit 0035a10

Browse files
committed
refactor: drop artifact field from SimulationOverrides
`SimulationOverrides.contracts` entries no longer carry a `ContractArtifact`. Simulation resolves the override-instance's `currentContractClassId` through PXE's locally registered classes, so callers register the target artifact via `pxe.registerContractClass(...)` once, then construct an instance with the desired `currentContractClassId` to drive dispatch. Migrates the in-tree account-stub flows (`cli-wallet`, `embedded_wallet`, `test_wallet`) to pre-register the stub class and bump `currentContractClassId` on the override instance. `proxied_contract_data_source` drops its `getFunctionArtifact*` overrides — function lookup falls through to the regular `ContractStore`.
1 parent 434c543 commit 0035a10

6 files changed

Lines changed: 162 additions & 120 deletions

File tree

docs/docs-developers/docs/resources/migration_notes.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,19 @@ If you relied on a bundled bare-name binary for general use:
151151

152152
If you set `Noir: Nargo Path` in the VS Code Noir extension to `$HOME/.aztec/current/bin/nargo`, change it to `$HOME/.aztec/current/bin/aztec-nargo` (the symlink is a drop-in for `nargo`). See the [Noir VSCode Extension guide](../aztec-nr/installation.md) for details.
153153

154+
### [Stdlib] `SimulationOverrides.contracts` entries no longer carry an artifact
155+
156+
`ContractOverrides` entries are now `{ instance }` only. To override a contract's artifact, pre-register the target class via `pxe.registerContractClass(artifact)` and set the override instance's `currentContractClassId` to that class id:
157+
158+
```diff
159+
- const instance = await getContractInstanceFromInstantiationParams(stubArtifact, { salt: Fr.random() });
160+
+ const instance = await pxe.getContractInstance(addr);
161+
+ await pxe.registerContractClass(stubArtifact);
162+
+ const stubClassId = (await getContractClassFromArtifact(stubArtifact)).id;
163+
- overrides = { contracts: { [addr.toString()]: { instance, artifact: stubArtifact } } };
164+
+ overrides = { contracts: { [addr.toString()]: { instance: { ...instance, currentContractClassId: stubClassId } } } };
165+
```
166+
154167
### [PXE] `proveTx` takes an options bag
155168

156169
`PXE.proveTx` used to accept `scopes` as a positional argument; it now takes an options bag consistent with `simulateTx` and `profileTx`, and adds an optional `senderForTags` field. Update direct callers:

yarn-project/cli-wallet/src/utils/wallet.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec
44
import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr';
55
import { getIdentities } from '@aztec/accounts/utils';
66
import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account';
7-
import {
8-
type InteractionFeeOptions,
9-
getContractInstanceFromInstantiationParams,
10-
getGasLimits,
11-
} from '@aztec/aztec.js/contracts';
7+
import { type InteractionFeeOptions, getContractClassFromArtifact, getGasLimits } from '@aztec/aztec.js/contracts';
128
import type { AztecNode } from '@aztec/aztec.js/node';
139
import { AccountManager, type Aliased, type SimulateOptions } from '@aztec/aztec.js/wallet';
1410
import { TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet';
@@ -21,6 +17,7 @@ import type { PXEConfig } from '@aztec/pxe/config';
2117
import type { PXE } from '@aztec/pxe/server';
2218
import { createPXE, getPXEConfig } from '@aztec/pxe/server';
2319
import { AztecAddress } from '@aztec/stdlib/aztec-address';
20+
import type { ContractClassIdPreimage, ContractClassWithId } from '@aztec/stdlib/contract';
2421
import { deriveSigningKey } from '@aztec/stdlib/keys';
2522
import { NoteDao } from '@aztec/stdlib/note';
2623
import type { SimulationOverrides, TxExecutionRequest, TxProvingResult } from '@aztec/stdlib/tx';
@@ -34,6 +31,11 @@ import { printGasEstimates } from './options/fees.js';
3431

3532
export class CLIWallet extends BaseWallet {
3633
private accountCache = new Map<string, Account>();
34+
/**
35+
* Per-stub-flavor cache of the stub class id and preimage. The Promise is stored (not the resolved
36+
* value) so concurrent first-time callers dedupe on the same hashing + registration work.
37+
*/
38+
#stubClasses = new Map<'schnorr' | 'ecdsa', Promise<ContractClassWithId & ContractClassIdPreimage>>();
3739

3840
constructor(
3941
pxe: PXE,
@@ -203,10 +205,28 @@ export class CLIWallet extends BaseWallet {
203205
}
204206
const { type } = await this.db!.retrieveAccount(address);
205207
const isSchnorr = type === 'schnorr';
206-
const artifact = isSchnorr ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact;
207208
const stubAccount = isSchnorr ? createStubSchnorrAccount(originalAddress) : createStubEcdsaAccount(originalAddress);
208-
const instance = await getContractInstanceFromInstantiationParams(artifact, { salt: Fr.random() });
209-
return { account: stubAccount, instance, artifact };
209+
const { id: stubClassId } = await this.#getStubClass(isSchnorr ? 'schnorr' : 'ecdsa');
210+
const instance = { ...contractInstance, currentContractClassId: stubClassId };
211+
return { account: stubAccount, instance };
212+
}
213+
214+
/**
215+
* Lazily hashes and registers the stub class for the given flavor, caching the result so
216+
* subsequent simulations skip the artifact-hashing + registration round-trip.
217+
*/
218+
#getStubClass(flavor: 'schnorr' | 'ecdsa'): Promise<ContractClassWithId & ContractClassIdPreimage> {
219+
let cached = this.#stubClasses.get(flavor);
220+
if (!cached) {
221+
cached = (async () => {
222+
const artifact = flavor === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact;
223+
const stubClass = await getContractClassFromArtifact(artifact);
224+
await this.pxe.registerContractClass(artifact);
225+
return stubClass;
226+
})();
227+
this.#stubClasses.set(flavor, cached);
228+
}
229+
return cached;
210230
}
211231

212232
override async simulateTx(
@@ -249,9 +269,9 @@ export class CLIWallet extends BaseWallet {
249269
const entrypoint = new DefaultEntrypoint();
250270
txRequest = await entrypoint.createTxExecutionRequest(finalExecutionPayload, feeOptions.gasSettings, chainInfo);
251271
} else {
252-
const { account, instance, artifact } = await this.getFakeAccountDataFor(from);
272+
const { account, instance } = await this.getFakeAccountDataFor(from);
253273
overrides = {
254-
contracts: { [from.toString()]: { instance, artifact } },
274+
contracts: { [from.toString()]: { instance } },
255275
};
256276
const executionOptions: DefaultAccountEntrypointOptions = {
257277
txNonce: Fr.random(),

yarn-project/end-to-end/src/test-wallet/test_wallet.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import { type PXEConfig, getPXEConfig } from '@aztec/pxe/config';
2525
import { PXE, type PXECreationOptions, createPXE } from '@aztec/pxe/server';
2626
import { AuthWitness } from '@aztec/stdlib/auth-witness';
2727
import { AztecAddress } from '@aztec/stdlib/aztec-address';
28-
import { getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract';
28+
import {
29+
type ContractClassIdPreimage,
30+
type ContractClassWithId,
31+
getContractClassFromArtifact,
32+
} from '@aztec/stdlib/contract';
2933
import { deriveSigningKey } from '@aztec/stdlib/keys';
3034
import type { NoteDao } from '@aztec/stdlib/note';
3135
import {
@@ -112,6 +116,30 @@ export class TestWallet extends BaseWallet {
112116
});
113117
}
114118

119+
/**
120+
* Per-account-type cache of the stub class id and preimage. The Promise is stored (not the resolved
121+
* value) so concurrent first-time callers dedupe on the same hashing + registration work.
122+
*/
123+
#stubClasses = new Map<AccountType, Promise<ContractClassWithId & ContractClassIdPreimage>>();
124+
125+
/**
126+
* Lazily hashes and registers the stub class for the given account type, caching the result so
127+
* subsequent simulations skip the artifact-hashing + registration round-trip.
128+
*/
129+
#getStubClass(type: AccountType): Promise<ContractClassWithId & ContractClassIdPreimage> {
130+
let cached = this.#stubClasses.get(type);
131+
if (!cached) {
132+
cached = (async () => {
133+
const stubArtifact = type === 'schnorr' ? StubSchnorrAccountContractArtifact : StubEcdsaAccountContractArtifact;
134+
const stubClass = await getContractClassFromArtifact(stubArtifact);
135+
await this.pxe.registerContractClass(stubArtifact);
136+
return stubClass;
137+
})();
138+
this.#stubClasses.set(type, cached);
139+
}
140+
return cached;
141+
}
142+
115143
/**
116144
* Builds contract overrides for all provided addresses by replacing their account contracts with stub implementations.
117145
*/
@@ -132,17 +160,10 @@ export class TestWallet extends BaseWallet {
132160
);
133161
}
134162

135-
const stubArtifact = this.getStubArtifactFor(address);
136-
const stubConstructorArgs =
137-
this.getTypeFor(address) === 'schnorr' ? [Fr.ZERO, Fr.ZERO] : [Buffer.alloc(32), Buffer.alloc(32)];
138-
const stubInstance = await getContractInstanceFromInstantiationParams(stubArtifact, {
139-
salt: Fr.random(),
140-
constructorArgs: stubConstructorArgs,
141-
});
163+
const { id: stubClassId } = await this.#getStubClass(this.getTypeFor(address));
142164

143165
contracts[address.toString()] = {
144-
instance: stubInstance,
145-
artifact: stubArtifact,
166+
instance: { ...contractInstance, currentContractClassId: stubClassId },
146167
};
147168
}
148169

@@ -155,12 +176,6 @@ export class TestWallet extends BaseWallet {
155176
return this.accounts.get(address.toString())?.type ?? 'schnorr';
156177
}
157178

158-
private getStubArtifactFor(address: AztecAddress) {
159-
return this.getTypeFor(address) === 'schnorr'
160-
? StubSchnorrAccountContractArtifact
161-
: StubEcdsaAccountContractArtifact;
162-
}
163-
164179
private getStubAccountFor(address: AztecAddress, completeAddress: CompleteAddress) {
165180
return this.getTypeFor(address) === 'schnorr'
166181
? createStubSchnorrAccount(completeAddress)

yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts

Lines changed: 49 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,69 @@
11
import { FunctionSelector } from '@aztec/stdlib/abi';
2-
import { AztecAddress } from '@aztec/stdlib/aztec-address';
2+
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
33
import type { ContractOverrides } from '@aztec/stdlib/tx';
44

55
import type { ContractStore } from '../storage/contract_store/contract_store.js';
66

77
/*
8-
* Proxy generator for a ContractStore that allows overriding contract instances and artifacts, so
9-
* the contract function simulator can execute different bytecode on certain addresses. An example use case
10-
* would be overriding your own account contract so that valid signatures don't have to be provided while simulating.
8+
* Proxy generator for a ContractStore that allows overriding contract instances at given addresses,
9+
* so the contract function simulator can execute different bytecode on certain addresses. An example
10+
* use case would be overriding your own account contract so that valid signatures don't have to be
11+
* provided while simulating.
12+
*
13+
* Function artifact lookups for an overridden address are routed via the override-instance's
14+
* `currentContractClassId` rather than the underlying ContractStore's address→class mapping —
15+
* `ContractStore.getFunctionArtifact` calls `this.getContractInstance` internally, which the proxy
16+
* cannot intercept (the binding sees the raw target, not the proxy). The target class must be
17+
* registered ahead of time via `pxe.registerContractClass(...)`.
1118
*/
1219
export class ProxiedContractStoreFactory {
1320
static create(contractStore: ContractStore, overrides?: ContractOverrides) {
1421
if (!overrides) {
1522
return contractStore;
1623
}
1724

25+
const findFunctionInArtifact = async (
26+
artifact: { name: string; functions: { name: string; parameters: any[] }[] },
27+
selector: FunctionSelector,
28+
contractAddress: AztecAddress,
29+
) => {
30+
for (const fn of artifact.functions) {
31+
const fnSelector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
32+
if (fnSelector.equals(selector)) {
33+
return { ...fn, contractName: artifact.name } as any;
34+
}
35+
}
36+
throw new Error(
37+
`Function with selector ${selector} not found in stub artifact for overridden contract at ${contractAddress}.`,
38+
);
39+
};
40+
1841
return new Proxy(contractStore, {
1942
get(target, prop: keyof ContractStore) {
20-
switch (prop) {
21-
case 'getContractInstance': {
22-
return async (address: AztecAddress) => {
23-
if (overrides[address.toString()]) {
24-
const { instance } = overrides[address.toString()]!;
25-
instance.address = address;
26-
const realInstance = await target.getContractInstance(address);
27-
if (!realInstance) {
28-
throw new Error(`Contract instance not found for address: ${address}`);
29-
}
30-
instance.currentContractClassId = realInstance.currentContractClassId;
31-
instance.originalContractClassId = realInstance.originalContractClassId;
32-
instance.initializationHash = realInstance.initializationHash;
33-
return instance;
34-
} else {
35-
return target.getContractInstance(address);
36-
}
37-
};
38-
}
39-
case 'getFunctionArtifact': {
40-
return async (contractAddress: AztecAddress, selector: FunctionSelector) => {
41-
if (overrides[contractAddress.toString()]) {
42-
const { artifact } = overrides[contractAddress.toString()]!;
43-
const functions = artifact.functions;
44-
for (let i = 0; i < functions.length; i++) {
45-
const fn = functions[i];
46-
const fnSelector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
47-
if (fnSelector.equals(selector)) {
48-
return fn;
49-
}
50-
}
51-
throw new Error(
52-
`Function with selector ${selector} not found in stub artifact for overridden contract at ${contractAddress}. The stub does not implement this function.`,
53-
);
54-
} else {
55-
return target.getFunctionArtifact(contractAddress, selector);
56-
}
57-
};
58-
}
59-
case 'getFunctionArtifactWithDebugMetadata': {
60-
return async (contractAddress: AztecAddress, selector: FunctionSelector) => {
61-
if (overrides[contractAddress.toString()]) {
62-
const { artifact } = overrides[contractAddress.toString()]!;
63-
const functions = artifact.functions;
64-
for (let i = 0; i < functions.length; i++) {
65-
const fn = functions[i];
66-
const fnSelector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
67-
if (fnSelector.equals(selector)) {
68-
return fn;
69-
}
70-
}
71-
throw new Error(
72-
`Function with selector ${selector} not found in stub artifact for overridden contract at ${contractAddress}. The stub does not implement this function.`,
73-
);
74-
} else {
75-
return target.getFunctionArtifactWithDebugMetadata(contractAddress, selector);
76-
}
77-
};
78-
}
79-
default: {
80-
const value = Reflect.get(target, prop);
81-
if (typeof value === 'function') {
82-
return value.bind(target);
43+
if (prop === 'getContractInstance') {
44+
return (address: AztecAddress) => {
45+
const override = overrides[address.toString()];
46+
return override ? Promise.resolve(override.instance) : target.getContractInstance(address);
47+
};
48+
}
49+
if (prop === 'getFunctionArtifact' || prop === 'getFunctionArtifactWithDebugMetadata') {
50+
return async (contractAddress: AztecAddress, selector: FunctionSelector) => {
51+
const override = overrides[contractAddress.toString()];
52+
if (!override) {
53+
return (target[prop] as Function).call(target, contractAddress, selector);
54+
}
55+
const artifact = await target.getContractArtifact(override.instance.currentContractClassId);
56+
if (!artifact) {
57+
throw new Error(
58+
`No artifact registered for override class ${override.instance.currentContractClassId} ` +
59+
`at ${contractAddress}. Register it via pxe.registerContractClass(...) before simulating.`,
60+
);
8361
}
84-
return value;
85-
}
62+
return findFunctionInArtifact(artifact, selector, contractAddress);
63+
};
8664
}
65+
const value = Reflect.get(target, prop);
66+
return typeof value === 'function' ? value.bind(target) : value;
8767
},
8868
}) satisfies ContractStore;
8969
}

yarn-project/stdlib/src/tx/simulated_tx.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { FieldsOf } from '@aztec/foundation/types';
33

44
import { z } from 'zod';
55

6-
import { type ContractArtifact, ContractArtifactSchema } from '../abi/abi.js';
76
import {
87
type ContractInstanceWithAddress,
98
ContractInstanceWithAddressSchema,
@@ -24,18 +23,17 @@ import { NestedProcessReturnValues, PublicSimulationOutput } from './public_simu
2423
import { Tx } from './tx.js';
2524

2625
/*
27-
* If passed during the execution of a user circuit, the contract function simulator will replace the instance and class
28-
* of the contract with the one provided in the overrides for that address. An example use case
29-
* would be overriding your own account contract so that valid signatures don't have to be provided while simulating.
26+
* If passed during the execution of a user circuit, the contract function simulator will replace
27+
* the contract instance at that address with the one provided. An example use case would be
28+
* overriding your own account contract so that valid signatures don't have to be provided while
29+
* simulating. The override's `currentContractClassId` resolves through PXE's locally registered
30+
* classes, so pre-register the target artifact via `pxe.registerContractClass(...)`.
3031
*/
31-
export type ContractOverrides = Record<
32-
string /* AztecAddress as string */,
33-
{ instance: ContractInstanceWithAddress; artifact: ContractArtifact }
34-
>;
32+
export type ContractOverrides = Record<string /* AztecAddress as string */, { instance: ContractInstanceWithAddress }>;
3533

3634
/*
3735
* Optional values that can be overridden during simulation. In order to simulate a transaction with these
38-
* set, it *must* be run without the kernel circuits, or validations will fail
36+
* set, it *must* be run without the kernel circuits, or validations will fail.
3937
*/
4038
export class SimulationOverrides {
4139
public contracts?: ContractOverrides;
@@ -47,12 +45,7 @@ export class SimulationOverrides {
4745
static get schema() {
4846
return z
4947
.object({
50-
contracts: optional(
51-
z.record(
52-
z.string(),
53-
z.object({ instance: ContractInstanceWithAddressSchema, artifact: ContractArtifactSchema }),
54-
),
55-
),
48+
contracts: optional(z.record(z.string(), z.object({ instance: ContractInstanceWithAddressSchema }))),
5649
})
5750
.transform(({ contracts }) => {
5851
return new SimulationOverrides(contracts);

0 commit comments

Comments
 (0)