Skip to content

Commit 6a9a9f7

Browse files
benesjanclaude
authored andcommitted
feat: stamping aztec version into contract artifacts (#22550)
Injects aztec stack version into Noir contract artifacts as it's a very useful info for debugging and other purposes (like verifying our backwards compatibility infra works). Closes [F-549](https://linear.app/aztec-labs/issue/F-549/include-aztecnr-and-stack-versions-in-contract-artifact) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 40ba340 commit 6a9a9f7

15 files changed

Lines changed: 98 additions & 9 deletions

File tree

noir-projects/noir-contracts/bootstrap.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ function get_contract_path {
9393
}
9494
export -f get_contract_path
9595

96+
# Stamps the aztec version into a contract artifact JSON in place. Mirrors stampAztecVersion in
97+
# yarn-project/aztec/src/cli/cmds/compile.ts so monorepo-built artifacts match those produced by `aztec compile`.
98+
# On release builds (REF_NAME is valid semver) the tag without the leading "v" is used; otherwise "dev".
99+
function stamp_aztec_version {
100+
local json_path=$1
101+
# "dev" here corresponds to DEV_VERSION in yarn-project/stdlib/src/update-checker/dev_version.ts.
102+
local version="dev"
103+
semver check "$REF_NAME" 2>/dev/null && version="${REF_NAME#v}"
104+
local tmp=$(mktemp)
105+
jq --arg v "$version" '.aztec_version = $v' "$json_path" > "$tmp"
106+
mv "$tmp" "$json_path"
107+
}
108+
export -f stamp_aztec_version
109+
96110
# This compiles a noir contract, transpiles public functions, strips internal prefixes,
97111
# and generates verification keys for private functions via 'bb aztec_process'.
98112
# $1 is the input package name, $2 is the folder name (e.g. "contracts" or "examples")
@@ -113,6 +127,9 @@ function compile {
113127
$BB aztec_process -i $json_path
114128
cache_upload contract-$contract_hash.tar.gz $json_path
115129
fi
130+
# Stamp the current version after the cache block so the field always matches the build's version, whether
131+
# the artifact came from a fresh compile or a cache hit.
132+
stamp_aztec_version "$json_path"
116133
}
117134
export -f compile
118135

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '@aztec/stdlib/contract';
99
import type { TxExecutionRequest, TxReceipt, UtilityExecutionResult } from '@aztec/stdlib/tx';
1010
import { OFFCHAIN_MESSAGE_IDENTIFIER } from '@aztec/stdlib/tx';
11+
import { DEV_VERSION } from '@aztec/stdlib/update-checker';
1112

1213
import { type MockProxy, mock } from 'jest-mock-extended';
1314

@@ -37,6 +38,7 @@ describe('Contract Class', () => {
3738

3839
const defaultArtifact: ContractArtifact = {
3940
name: 'FooContract',
41+
aztecVersion: DEV_VERSION,
4042
functions: [
4143
{
4244
name: 'bar',

yarn-project/aztec.js/src/contract/deploy_method.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract';
55
import { Gas } from '@aztec/stdlib/gas';
66
import { PublicKeys } from '@aztec/stdlib/keys';
77
import { OFFCHAIN_MESSAGE_IDENTIFIER, type OffchainEffect } from '@aztec/stdlib/tx';
8+
import { DEV_VERSION } from '@aztec/stdlib/update-checker';
89

910
import { type MockProxy, mock } from 'jest-mock-extended';
1011

@@ -18,6 +19,7 @@ describe('DeployMethod', () => {
1819

1920
const artifact: ContractArtifact = {
2021
name: 'TestContract',
22+
aztecVersion: DEV_VERSION,
2123
functions: [
2224
{
2325
name: 'constructor',

yarn-project/aztec.js/src/scripts/generate_protocol_contract_types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ function generateProtocolContractArtifact(input: ContractArtifact): string {
6363

6464
return `{
6565
name: '${input.name}',
66+
aztecVersion: '${input.aztecVersion}',
6667
functions: [
6768
${functionsArray}
6869
],

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
TxSimulationResult,
1919
UtilityExecutionResult,
2020
} from '@aztec/stdlib/tx';
21+
import { DEV_VERSION } from '@aztec/stdlib/update-checker';
2122

2223
import {
2324
type InteractionWaitOptions,
@@ -126,6 +127,7 @@ describe('WalletSchema', () => {
126127
it('registerContract', async () => {
127128
const mockArtifact: ContractArtifact = {
128129
name: 'TestContract',
130+
aztecVersion: DEV_VERSION,
129131
functions: [],
130132
nonDispatchPublicFunctions: [],
131133
outputs: { structs: {}, globals: {} },
@@ -318,6 +320,7 @@ describe('WalletSchema', () => {
318320

319321
const mockArtifact: ContractArtifact = {
320322
name: 'TestContract',
323+
aztecVersion: DEV_VERSION,
321324
functions: [],
322325
nonDispatchPublicFunctions: [],
323326
outputs: { structs: {}, globals: {} },

yarn-project/aztec/src/cli/cmds/compile.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { findBbBinary } from '@aztec/bb.js';
22
import type { LogFn } from '@aztec/foundation/log';
3+
import { getPackageVersion } from '@aztec/stdlib/update-checker';
34

45
import { execFileSync } from 'child_process';
56
import type { Command } from 'commander';
6-
import { readFile } from 'fs/promises';
7+
import { readFile, writeFile } from 'fs/promises';
78
import { join } from 'path';
89

910
import { readArtifactFiles } from './utils/artifacts.js';
@@ -25,6 +26,17 @@ async function collectContractArtifacts(): Promise<string[]> {
2526
return files.filter(f => Array.isArray(f.content.functions)).map(f => f.filePath);
2627
}
2728

29+
/** Stamps the Aztec stack version into the contract artifacts. */
30+
async function stampAztecVersion(artifactPaths: string[]): Promise<void> {
31+
const version = getPackageVersion();
32+
for (const path of artifactPaths) {
33+
const artifact = JSON.parse(await readFile(path, 'utf-8'));
34+
// eslint-disable-next-line camelcase
35+
artifact.aztec_version = version;
36+
await writeFile(path, JSON.stringify(artifact, null, 2) + '\n');
37+
}
38+
}
39+
2840
/** Returns the set of package names that are contract crates in the current workspace. */
2941
async function getContractPackageNames(): Promise<Set<string>> {
3042
const contractNames = new Set<string>();
@@ -148,6 +160,8 @@ async function compileAztecContract(nargoArgs: string[], log: LogFn): Promise<vo
148160
log('Postprocessing contracts...');
149161
const bbArgs = artifacts.flatMap(a => ['-i', a]);
150162
await run(bb, ['aztec_process', ...bbArgs]);
163+
164+
await stampAztecVersion(artifacts);
151165
}
152166

153167
log('Compilation complete!');

yarn-project/end-to-end/src/fixtures/setup.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { protocolContractsHash } from '@aztec/protocol-contracts';
4747
import type { ProverNodeConfig } from '@aztec/prover-node';
4848
import { type PXEConfig, getPXEConfig } from '@aztec/pxe/server';
4949
import type { SequencerClient } from '@aztec/sequencer-client';
50+
import { ARTIFACT_VERSION_BEFORE_INJECTION } from '@aztec/stdlib/abi';
5051
import { type ContractInstanceWithAddress, getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract';
5152
import type { AztecNodeAdmin, AztecNodeDebug } from '@aztec/stdlib/interfaces/client';
5253
import { tryStop } from '@aztec/stdlib/interfaces/server';
@@ -260,6 +261,32 @@ export type EndToEndContext = {
260261
teardown: () => Promise<void>;
261262
};
262263

264+
/**
265+
* When CONTRACT_ARTIFACTS_VERSION is set (backwards compatibility testing), asserts that the loaded artifact's
266+
* aztecVersion matches the expected version. This is a sanity check verifying that the legacy artifact resolver
267+
* actually swapped in the correct version.
268+
*/
269+
function assertContractArtifactsVersion() {
270+
const expected = process.env.CONTRACT_ARTIFACTS_VERSION;
271+
if (!expected) {
272+
return;
273+
}
274+
const { aztecVersion } = SponsoredFPCContract.artifact;
275+
// TODO(F-557): Remove this bypass once pre-version artifacts are no longer tested.
276+
if (aztecVersion === ARTIFACT_VERSION_BEFORE_INJECTION) {
277+
createLogger('e2e:setup').info(
278+
`Skipping artifact version check: artifact predates version injection (CONTRACT_ARTIFACTS_VERSION=${expected})`,
279+
);
280+
return;
281+
}
282+
if (aztecVersion !== expected) {
283+
throw new Error(
284+
`Artifact version mismatch: expected ${expected} but got ${aztecVersion}. ` +
285+
`The legacy artifact resolver may not have swapped in the correct version.`,
286+
);
287+
}
288+
}
289+
263290
/**
264291
* Sets up the environment for the end-to-end tests.
265292
* @param numberOfAccounts - The number of new accounts to be created once the PXE is initiated.
@@ -272,6 +299,7 @@ export async function setup(
272299
pxeOpts: Partial<PXEConfig> = {},
273300
chain: Chain = foundry,
274301
): Promise<EndToEndContext> {
302+
assertContractArtifactsVersion();
275303
let anvil: Anvil | undefined;
276304
try {
277305
opts.aztecTargetCommitteeSize ??= 0;

yarn-project/stdlib/src/abi/abi.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { schemas, zodFor } from '@aztec/foundation/schemas';
66
import { inflate } from 'pako';
77
import { z } from 'zod';
88

9+
import { DEV_VERSION } from '../update-checker/dev_version.js';
910
import { FunctionSelector } from './function_selector.js';
1011

1112
/** A basic value. */
@@ -384,11 +385,17 @@ export type FieldLayout = {
384385
slot: Fr;
385386
};
386387

388+
/** Placeholder version injected into artifacts compiled before aztecVersion was added. TODO(F-557): Remove. */
389+
export const ARTIFACT_VERSION_BEFORE_INJECTION = 'FROM_RELEASE_BEFORE_VERSION_INJECTION';
390+
387391
/** Defines artifact of a contract. */
388392
export interface ContractArtifact {
389393
/** The name of the contract. */
390394
name: string;
391395

396+
/** The version of the Aztec stack that compiled this artifact. */
397+
aztecVersion: string;
398+
392399
/** The functions of the contract. Includes private and utility functions, plus the public dispatch function. */
393400
functions: FunctionArtifact[];
394401

@@ -411,6 +418,7 @@ export interface ContractArtifact {
411418
export const ContractArtifactSchema = zodFor<ContractArtifact>()(
412419
z.object({
413420
name: z.string(),
421+
aztecVersion: z.string().default(ARTIFACT_VERSION_BEFORE_INJECTION), // TODO(F-557): Remove default.
414422
functions: z.array(FunctionArtifactSchema),
415423
nonDispatchPublicFunctions: z.array(FunctionAbiSchema),
416424
outputs: z.object({
@@ -604,6 +612,7 @@ export function emptyFunctionArtifact(): FunctionArtifact {
604612
export function emptyContractArtifact(): ContractArtifact {
605613
return {
606614
name: '',
615+
aztecVersion: DEV_VERSION,
607616
functions: [emptyFunctionArtifact()],
608617
nonDispatchPublicFunctions: [emptyFunctionAbi()],
609618
outputs: {

yarn-project/stdlib/src/abi/contract_artifact.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import {
1313
type ABIParameter,
1414
type ABIParameterVisibility,
15+
ARTIFACT_VERSION_BEFORE_INJECTION,
1516
type AbiType,
1617
type BasicValue,
1718
type ContractArtifact,
@@ -51,6 +52,10 @@ export function contractArtifactFromBuffer(buffer: Buffer): ContractArtifact {
5152
*/
5253
export function loadContractArtifact(input: NoirCompiledContract): ContractArtifact {
5354
if (isContractArtifact(input)) {
55+
// TODO(F-557): Remove this fallback once pre-version artifacts are no longer tested.
56+
if (!(input as unknown as Record<string, unknown>).aztecVersion) {
57+
return { ...input, aztecVersion: ARTIFACT_VERSION_BEFORE_INJECTION };
58+
}
5459
return input;
5560
}
5661
return generateContractArtifact(input);
@@ -276,10 +281,9 @@ function getStorageLayout(input: NoirCompiledContract) {
276281
}
277282

278283
/**
279-
* Given a Nargo output generates an Aztec-compatible contract artifact.
284+
* Given a post-processed Nargo output defined as `contract` generates an Aztec-compatible contract artifact.
285+
*
280286
* Does not include public bytecode, apart from the public_dispatch function.
281-
* @param compiled - Noir build output.
282-
* @returns Aztec contract build artifact.
283287
*/
284288
function generateContractArtifact(contract: NoirCompiledContract): ContractArtifact {
285289
try {
@@ -288,6 +292,7 @@ function generateContractArtifact(contract: NoirCompiledContract): ContractArtif
288292
}
289293
return ContractArtifactSchema.parse({
290294
name: contract.name,
295+
aztecVersion: contract.aztec_version,
291296
functions: contract.functions.filter(f => retainBytecode(f)).map(f => generateFunctionArtifact(f, contract)),
292297
nonDispatchPublicFunctions: contract.functions
293298
.filter(f => !retainBytecode(f))
@@ -302,15 +307,15 @@ function generateContractArtifact(contract: NoirCompiledContract): ContractArtif
302307
}
303308

304309
/**
305-
* Given a Nargo output generates an Aztec-compatible contract artifact.
310+
* Given a post-processed Nargo output defined as `contract` generates an Aztec-compatible contract artifact.
311+
*
306312
* Retains all public bytecode.
307-
* @param compiled - Noir build output.
308-
* @returns Aztec contract build artifact.
309313
*/
310314
function generateContractArtifactForPublic(contract: NoirCompiledContract): ContractArtifact {
311315
try {
312316
return ContractArtifactSchema.parse({
313317
name: contract.name,
318+
aztecVersion: contract.aztec_version,
314319
functions: contract.functions.map(f => generateFunctionArtifact(f, contract)),
315320
nonDispatchPublicFunctions: contract.functions
316321
.filter(f => !retainBytecode(f))

yarn-project/stdlib/src/contract/artifact_hash.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ContractArtifact } from '../abi/index.js';
22
import { getTestContractArtifact } from '../tests/fixtures.js';
3+
import { DEV_VERSION } from '../update-checker/dev_version.js';
34
import { computeArtifactHash } from './artifact_hash.js';
45

56
describe('ArtifactHash', () => {
@@ -9,6 +10,7 @@ describe('ArtifactHash', () => {
910
functions: [],
1011
nonDispatchPublicFunctions: [],
1112
name: 'Test',
13+
aztecVersion: DEV_VERSION,
1214
outputs: {
1315
globals: {},
1416
structs: {},

0 commit comments

Comments
 (0)