Skip to content

Commit ca0d2ce

Browse files
committed
feat: expose wallet.registerContractClass for simulating contract upgrades
Adds `wallet.registerContractClass(artifact)` (a thin pass-through to PXE) so callers can register a new class artifact locally before passing an instance override that targets it. Without this, PXE-side ACIR dispatch can't resolve private functions of the override's class. With `fastForwardContractUpdate` (downstack) plus this method, a single `.simulate({ overrides })` covers both private and public function calls on an upgraded contract: ```ts await wallet.registerContractClass(UpdatedContract.artifact); const overrides = await fastForwardContractUpdate({ instanceAddress, newClassId, node }); await updatedContract.methods.set_private_value().simulate({ from, overrides }); ```
1 parent 2f092e4 commit ca0d2ce

5 files changed

Lines changed: 54 additions & 0 deletions

File tree

yarn-project/aztec.js/src/wallet/wallet.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ describe('WalletSchema', () => {
155155
});
156156
});
157157

158+
it('registerContractClass', async () => {
159+
const mockArtifact: ContractArtifact = {
160+
name: 'TestContract',
161+
functions: [],
162+
nonDispatchPublicFunctions: [],
163+
outputs: { structs: {}, globals: {} },
164+
fileMap: {},
165+
storageLayout: {},
166+
};
167+
await context.client.registerContractClass(mockArtifact);
168+
});
169+
158170
it('simulateTx', async () => {
159171
const exec: ExecutionPayload = {
160172
calls: [],
@@ -448,6 +460,8 @@ class MockWallet implements Wallet {
448460
};
449461
}
450462

463+
async registerContractClass(_artifact: any): Promise<void> {}
464+
451465
async simulateTx(_exec: ExecutionPayload, _opts: SimulateOptions): Promise<TxSimulationResultWithAppOffset> {
452466
return TxSimulationResultWithAppOffset.fromResultAndOffset(await TxSimulationResult.random(), 0);
453467
}

yarn-project/aztec.js/src/wallet/wallet.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@ export type Wallet = {
272272
artifact?: ContractArtifact,
273273
secretKey?: Fr,
274274
): Promise<ContractInstanceWithAddress>;
275+
/**
276+
* Registers a contract class artifact in the local PXE without binding it to any instance.
277+
* Useful for simulation flows that need the artifact available locally before any on-chain
278+
* upgrade has taken effect. The artifact's class id must match its content; no chain check.
279+
*/
280+
registerContractClass(artifact: ContractArtifact): Promise<void>;
275281
simulateTx(exec: ExecutionPayload, opts: SimulateOptions): Promise<TxSimulationResultWithAppOffset>;
276282
executeUtility(call: FunctionCall, opts: ExecuteUtilityOptions): Promise<UtilityExecutionResult>;
277283
profileTx(exec: ExecutionPayload, opts: ProfileOptions): Promise<TxProfileResult>;
@@ -559,6 +565,7 @@ const WalletMethodSchemas = {
559565
.function()
560566
.args(ContractInstanceWithAddressSchema, optional(ContractArtifactSchema), optional(schemas.Fr))
561567
.returns(ContractInstanceWithAddressSchema),
568+
registerContractClass: z.function().args(ContractArtifactSchema).returns(z.void()),
562569
simulateTx: z
563570
.function()
564571
.args(ExecutionPayloadSchema, SimulateOptionsSchema)

yarn-project/end-to-end/src/e2e_contract_updates.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,29 @@ describe('e2e_contract_updates', () => {
208208
contract.methods.set_public_value(5678n).simulate({ from: defaultAccountAddress }),
209209
).resolves.toBeDefined();
210210
});
211+
212+
// UpdatedContract.set_private_value is a private function that doesn't exist on UpdatableContract.
213+
// For PXE-side ACIR dispatch to find it, the artifact must be registered locally first via
214+
// wallet.registerContractClass; the helper itself only takes the class id.
215+
it('fastForwardContractUpdate enables simulation of post-upgrade private calls', async () => {
216+
const updatedContract = UpdatedContract.at(contract.address, wallet);
217+
218+
// Without overrides (and without local artifact registration), the new private function isn't
219+
// available on the deployed class.
220+
await expect(
221+
updatedContract.methods.set_private_value().simulate({ from: defaultAccountAddress }),
222+
).rejects.toThrow();
223+
224+
// Register the new artifact in the local PXE so the ACIR simulator can find its private functions.
225+
await wallet.registerContractClass(UpdatedContract.artifact);
226+
227+
const overrides = await fastForwardContractUpdate({
228+
instanceAddress: contract.address,
229+
newClassId: updatedContractClassId,
230+
node: aztecNode,
231+
});
232+
await expect(
233+
updatedContract.methods.set_private_value().simulate({ from: defaultAccountAddress, overrides }),
234+
).resolves.toBeDefined();
235+
});
211236
});

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ export class WorkerWallet implements Wallet {
166166
return this.call('registerContract', instance, artifact, secretKey);
167167
}
168168

169+
registerContractClass(artifact: ContractArtifact): Promise<void> {
170+
return this.call('registerContractClass', artifact);
171+
}
172+
169173
simulateTx(exec: ExecutionPayload, opts: SimulateOptions): Promise<TxSimulationResultWithAppOffset> {
170174
return this.call('simulateTx', exec, opts);
171175
}

yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ export abstract class BaseWallet implements Wallet {
348348
return instance;
349349
}
350350

351+
registerContractClass(artifact: ContractArtifact): Promise<void> {
352+
return this.pxe.registerContractClass(artifact);
353+
}
354+
351355
/**
352356
* Simulates calls through the standard PXE path (account entrypoint).
353357
* @param executionPayload - The execution payload to simulate.

0 commit comments

Comments
 (0)