Skip to content

Commit 6b44043

Browse files
committed
fix(explorer-api): support v5 contract verification
1 parent a99e001 commit 6b44043

10 files changed

Lines changed: 169 additions & 346 deletions

File tree

packages/contract-verification/src/artifact/index.test.ts

Lines changed: 28 additions & 278 deletions
Large diffs are not rendered by default.
Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,64 @@
1+
import { AztecAddress } from "@aztec/aztec.js/addresses";
2+
import { loadContractArtifact } from "@aztec/aztec.js/abi";
13
import {
2-
NoirCompiledContract,
3-
loadContractArtifact,
4-
} from "@aztec/aztec.js/abi";
5-
import contractArtifactJson from "@aztec/noir-contracts.js/artifacts/private_voting_contract-PrivateVoting" with { type: "json" };
6-
import { beforeAll, describe, expect, test } from "vitest";
7-
import { VerifyInstanceDeploymentPayload } from "../types.js";
4+
getContractClassFromArtifact,
5+
getContractInstanceFromInstantiationParams,
6+
} from "@aztec/aztec.js/contracts";
7+
import { Fr } from "@aztec/aztec.js/fields";
8+
import publicChecksArtifactJson from "@aztec/noir-contracts.js/artifacts/public_checks_contract-PublicChecks" with { type: "json" };
9+
import { describe, expect, test } from "vitest";
810
import { generateVerifyInstancePayload } from "./generate-payload.js";
911
import { verifyInstanceDeploymentPayload } from "./verify-payload.js";
10-
import { getContractClassFromArtifact } from "@aztec/aztec.js/contracts";
1112

12-
const salt =
13-
"0x22a286727cc52b2af208b884a01858793d29d20e897df4b2a80237d96528c8de";
14-
const adminAddress =
15-
"0x2e16425c902f899df2b77bacc911e75e9acb5f0d4e3866303e372b2ed44545d9";
16-
const publicKeyValues =
17-
"0x01498945581e0eb9f8427ad6021184c700ef091d570892c437d12c7d90364bbd170ae506787c5c43d6ca9255d571c10fa9ffa9d141666e290c347c5c9ab7e34400c044b05b6ca83b9c2dbae79cc1135155956a64e136819136e9947fe5e5866c1c1f0ca244c7cd46b682552bff8ae77dea40b966a71de076ec3b7678f2bdb1511b00316144359e9a3ec8e49c1cdb7eeb0cedd190dfd9dc90eea5115aa779e287080ffc74d7a8b0bccb88ac11f45874172f3847eb8b92654aaa58a3d2b8dc7833019c111f36ad3fc1d9b7a7a14344314d2864b94f030594cd67f753ef774a1efb2039907fe37f08d10739255141bb066c506a12f7d1e8dfec21abc58494705b6f";
18-
const instanceAddress =
19-
"0x02a8a8fd9e12fb24265a20109f42fa1afd51d7a1bc88dd163673e91ba38698c0";
20-
const immutablesHash =
21-
"0x0000000000000000000000000000000000000000000000000000000000000000";
13+
describe("v5 instance deployment verification", () => {
14+
test("verifies an official Aztec v5 contract instance", async () => {
15+
const artifact = loadContractArtifact(publicChecksArtifactJson);
16+
const contractClass = await getContractClassFromArtifact(artifact);
17+
const instance = await getContractInstanceFromInstantiationParams(artifact, {
18+
constructorArgs: [],
19+
deployer: AztecAddress.ZERO,
20+
salt: Fr.random(),
21+
});
22+
const payload = generateVerifyInstancePayload({
23+
publicKeysString: instance.publicKeys.toString(),
24+
deployer: instance.deployer.toString(),
25+
salt: instance.salt.toString(),
26+
constructorArgs: [],
27+
});
2228

23-
describe.skip("verify instance deployment", () => {
24-
let payload: VerifyInstanceDeploymentPayload;
25-
let generatingPayloadError: Error;
26-
let verifyInstanceDeploymentPayloadResult: boolean;
27-
let verifyInstanceDeploymentPayloadError: Error;
28-
let contractClass;
29-
beforeAll(async () => {
30-
const loadedArtifact = loadContractArtifact(contractArtifactJson);
31-
contractClass = await getContractClassFromArtifact(loadedArtifact);
32-
try {
33-
payload = generateVerifyInstancePayload({
34-
publicKeysString: publicKeyValues,
35-
deployer: adminAddress,
36-
salt,
37-
constructorArgs: [adminAddress],
38-
artifactObj: contractArtifactJson,
39-
});
40-
} catch (error) {
41-
generatingPayloadError = error as Error;
42-
}
43-
44-
try {
45-
verifyInstanceDeploymentPayloadResult =
46-
await verifyInstanceDeploymentPayload({
47-
...payload,
48-
stringifiedArtifactJson: JSON.stringify(
49-
contractArtifactJson as unknown as NoirCompiledContract,
50-
),
51-
instanceAddress,
52-
contractClassId: contractClass.id.toString(),
53-
immutablesHash,
54-
});
55-
} catch (error) {
56-
verifyInstanceDeploymentPayloadError = error as Error;
57-
}
58-
});
59-
test("generate payload without error", () => {
60-
expect(generatingPayloadError).toBeUndefined();
61-
});
62-
test("verify payload without error", () => {
63-
expect(verifyInstanceDeploymentPayloadError).toBeUndefined();
29+
await expect(
30+
verifyInstanceDeploymentPayload({
31+
...payload,
32+
stringifiedArtifactJson: JSON.stringify(publicChecksArtifactJson),
33+
instanceAddress: instance.address.toString(),
34+
contractClassId: contractClass.id.toString(),
35+
immutablesHash: instance.immutablesHash.toString(),
36+
}),
37+
).resolves.toBe(true);
6438
});
65-
test("verify payload result", () => {
66-
expect(verifyInstanceDeploymentPayloadResult).toBe(true);
39+
40+
test("rejects the current class id when it differs from the original class id", async () => {
41+
const artifact = loadContractArtifact(publicChecksArtifactJson);
42+
const instance = await getContractInstanceFromInstantiationParams(artifact, {
43+
constructorArgs: [],
44+
deployer: AztecAddress.ZERO,
45+
salt: Fr.random(),
46+
});
47+
const payload = generateVerifyInstancePayload({
48+
publicKeysString: instance.publicKeys.toString(),
49+
deployer: instance.deployer.toString(),
50+
salt: instance.salt.toString(),
51+
constructorArgs: [],
52+
});
53+
54+
await expect(
55+
verifyInstanceDeploymentPayload({
56+
...payload,
57+
stringifiedArtifactJson: JSON.stringify(publicChecksArtifactJson),
58+
instanceAddress: instance.address.toString(),
59+
contractClassId: Fr.ZERO.toString(),
60+
immutablesHash: instance.immutablesHash.toString(),
61+
}),
62+
).resolves.toBe(false);
6763
});
6864
});

services/event-cannon/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ yarn build
7171
| `SCENARIO_DEPLOY_AND_UPDATE` | `false` | Enable as needed | Deploy and update contract |
7272
| `SCENARIO_AZTEC_STANDARD_TOKEN_CONTRACT_LEGACY` | `false` | Enable as needed | Deploy and register legacy standard token |
7373
| `SCENARIO_AZTEC_STANDARD_TOKEN_CONTRACT_CURRENT` | `false` | Enable as needed | Deploy and validate current standard tokens (`mockEUR`, `mockUSD`, `mockGBP`) |
74+
| `SCENARIO_V5_CONTRACT_VERIFICATION` | `false` | Enable as needed | Strictly verify the official v5 `PublicChecks` class and instance |
7475

7576
The `EXPLORER_API_URL` requires the API key in the path because the ingress uses the URL pattern `/v1/<api-key>/<path>`. The auth service validates the key and the ingress rewrites the URL to strip it before forwarding to the backend. Use `dev-api-key` for local development.
7677

services/event-cannon/src/cannon/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
SCENARIO_TOKEN_CONTRACT,
1414
SCENARIO_VERIFY_SOURCE_CODE,
1515
SCENARIO_EXPLORER_SHOWCASE,
16+
SCENARIO_V5_CONTRACT_VERIFICATION,
1617
} from "../environment.js";
1718
import { logger } from "../logger.js";
1819
import { setup } from "./pxe.js";
@@ -22,6 +23,7 @@ let isShutdown = false;
2223
const scenariosToRun: {
2324
envVar: string;
2425
scenario: () => Promise<void>;
26+
failOnError?: boolean;
2527
}[] = [];
2628

2729
export async function init() {
@@ -109,6 +111,14 @@ export async function init() {
109111
});
110112
}
111113

114+
if (SCENARIO_V5_CONTRACT_VERIFICATION) {
115+
scenariosToRun.push({
116+
envVar: "SCENARIO_V5_CONTRACT_VERIFICATION",
117+
scenario: scenarios.verifyV5ReferenceContract,
118+
failOnError: true,
119+
});
120+
}
121+
112122
logger.info(`
113123
SCENARIO_DELAY: ${SCENARIO_DELAY / 1000} seconds
114124
INIFINITE_LOOP: ${INIFINITE_LOOP ? "✅" : "❌"}
@@ -127,6 +137,7 @@ SCENARIO_AZTEC_STANDARD_TOKEN_CONTRACT_LEGACY: ${SCENARIO_AZTEC_STANDARD_TOKEN_C
127137
SCENARIO_AZTEC_STANDARD_TOKEN_CONTRACT_CURRENT: ${SCENARIO_AZTEC_STANDARD_TOKEN_CONTRACT_CURRENT ? "✅" : "❌"}
128138
SCENARIO_VERIFY_SOURCE_CODE: ${SCENARIO_VERIFY_SOURCE_CODE ? "✅" : "❌"}
129139
SCENARIO_EXPLORER_SHOWCASE: ${SCENARIO_EXPLORER_SHOWCASE ? "✅" : "❌"}
140+
SCENARIO_V5_CONTRACT_VERIFICATION: ${SCENARIO_V5_CONTRACT_VERIFICATION ? "✅" : "❌"}
130141
`);
131142

132143
await setup();
@@ -151,6 +162,9 @@ const runScenarios = async () => {
151162
} catch (e) {
152163
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
153164
logger.error(`Error running scenario: ${(e as Error).stack ?? e}`);
165+
if (scenario.failOnError) {
166+
throw e;
167+
}
154168
}
155169
logger.info(
156170
`waiting ${SCENARIO_DELAY / 1000} seconds before next scenario...`,

services/event-cannon/src/cannon/scenarios/deploy-and-interact-vote-contract.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ export async function run() {
4343
contractArtifactJson,
4444
contractInstance.currentContractClassId.toString(),
4545
contractInstance.version,
46+
{ throwOnError: true },
4647
);
4748

48-
// Fire-and-forget verification; do not block scenario execution.
49-
void verifyContractInstanceDeployment({
49+
await verifyContractInstanceDeployment({
5050
contractLoggingName,
5151
contractInstanceAddress: contract.address.toString(),
5252
verifyArgs: {
@@ -65,6 +65,7 @@ export async function run() {
6565
repoUrl: "https://github.com/AztecProtocol/aztec-packages",
6666
reviewedAt: new Date(),
6767
},
68+
throwOnError: true,
6869
});
6970

7071
const votingContractAlice = Contract.at(

services/event-cannon/src/cannon/scenarios/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export async function deployAztecStandardTokenContractLegacy() {
2222
export { run as verifySourceCode } from "./verify-source-code.js";
2323
export { run as deployAndInteractWithFpc } from "./deploy-and-interact-with-fpc.js";
2424
export { run as deployAndInteractExplorerShowcase } from "./deploy-and-interact-explorer-showcase.js";
25+
export { run as verifyV5ReferenceContract } from "./verify-v5-reference-contract.js";

services/event-cannon/src/cannon/scenarios/utils/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,20 +295,29 @@ export const registerContractClassArtifact = async (
295295
artifactObj: { default: NoirCompiledContract } | NoirCompiledContract,
296296
contractClassId: string,
297297
_instanceVersion: number,
298+
options?: { throwOnError?: boolean },
298299
) => {
299300
const url = generateVerifyArtifactUrl(
300301
EXPLORER_API_URL,
301302
contractClassId,
302303
AZTEC_CONTRACT_CLASS_VERSION,
303304
);
304305
const postData = JSON.stringify(generateVerifyArtifactPayload(artifactObj));
305-
await callExplorerApi({
306+
const res = await callExplorerApi({
306307
loggingString: `📜 registerContractClassArtifact ${contractLoggingName}`,
307308
urlStr: url,
308309
postData,
309310
method: "POST",
310311
waitForIndexing: true,
311312
});
313+
if (
314+
options?.throwOnError &&
315+
!(res.statusCode === 200 || res.statusCode === 201 || res.statusCode === 202)
316+
) {
317+
throw new Error(
318+
`registerContractClassArtifact failed (${contractLoggingName}): ${res.statusCode} ${res.statusMessage} ${res.data}`,
319+
);
320+
}
312321
};
313322

314323
export const registerStandardContractArtifact = async (
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { PublicChecksContract } from "@aztec/noir-contracts.js/PublicChecks";
2+
import * as contractArtifactJson from "@aztec/noir-contracts.js/artifacts/public_checks_contract-PublicChecks" with { type: "json" };
3+
import { logger } from "../../logger.js";
4+
import { getAccounts, getAztecNodeClient, getWallet } from "../pxe.js";
5+
import {
6+
deployContract,
7+
registerContractClassArtifact,
8+
verifyContractInstanceDeployment,
9+
} from "./utils/index.js";
10+
11+
const contractId = "V5PublicChecksReference";
12+
13+
export async function run() {
14+
logger.info(`===== ${contractId} =====`);
15+
const wallet = getWallet();
16+
const deployer = getAccounts().alice.address;
17+
const { contract, instance } = await deployContract({
18+
contractLoggingName: contractId,
19+
deployFn: () => PublicChecksContract.deploy(wallet),
20+
from: deployer,
21+
node: getAztecNodeClient(),
22+
});
23+
24+
await registerContractClassArtifact(
25+
contractId,
26+
contractArtifactJson,
27+
instance.originalContractClassId.toString(),
28+
instance.version,
29+
{ throwOnError: true },
30+
);
31+
await verifyContractInstanceDeployment({
32+
contractLoggingName: contractId,
33+
contractInstanceAddress: contract.address.toString(),
34+
verifyArgs: {
35+
publicKeysString: instance.publicKeys.toString(),
36+
deployer: instance.deployer.toString(),
37+
salt: instance.salt.toString(),
38+
constructorArgs: [],
39+
},
40+
throwOnError: true,
41+
});
42+
43+
logger.info(
44+
`✅ Verified official Aztec v5 reference contract ${contract.address.toString()}`,
45+
);
46+
}

services/event-cannon/src/environment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ export const SCENARIO_VERIFY_SOURCE_CODE =
4242
process.env.SCENARIO_VERIFY_SOURCE_CODE === "true";
4343
export const SCENARIO_EXPLORER_SHOWCASE =
4444
process.env.SCENARIO_EXPLORER_SHOWCASE === "true";
45+
export const SCENARIO_V5_CONTRACT_VERIFICATION =
46+
process.env.SCENARIO_V5_CONTRACT_VERIFICATION === "true";

services/explorer-api/src/svcs/http-server/routes/controllers/contract-instances.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,14 +382,15 @@ export const POST_L2_VERIFY_CONTRACT_INSTANCE_DEPLOYMENT = asyncHandler(
382382
res.status(400).send("Uploaded deployer does not match the DB");
383383
return;
384384
}
385+
const deploymentContractClassId = dbContractInstance.originalContractClassId;
385386
const contractClassString = await dbWrapper.get(
386387
contractClassWithArtifactKeys(
387-
dbContractInstance.currentContractClassId,
388+
deploymentContractClassId,
388389
dbContractInstance.version,
389390
),
390391
() =>
391392
db.l2Contract.getL2RegisteredContractClass(
392-
dbContractInstance.currentContractClassId,
393+
deploymentContractClassId,
393394
dbContractInstance.version,
394395
),
395396
);
@@ -407,7 +408,7 @@ export const POST_L2_VERIFY_CONTRACT_INSTANCE_DEPLOYMENT = asyncHandler(
407408
res
408409
.status(400)
409410
.send(
410-
`artifactJson is missing in the request and could not be found for contract class ${dbContractInstance.currentContractClassId} version ${dbContractInstance.version}`,
411+
`artifactJson is missing in the request and could not be found for contract class ${deploymentContractClassId} version ${dbContractInstance.version}`,
411412
);
412413
return;
413414
}
@@ -450,7 +451,7 @@ export const POST_L2_VERIFY_CONTRACT_INSTANCE_DEPLOYMENT = asyncHandler(
450451

451452
setEntry(
452453
contractClassWithArtifactKeys(
453-
dbContractInstance.currentContractClassId,
454+
deploymentContractClassId,
454455
dbContractInstance.version,
455456
),
456457
JSON.stringify(completeContractClass),
@@ -501,7 +502,9 @@ export const POST_L2_VERIFY_CONTRACT_INSTANCE_DEPLOYMENT = asyncHandler(
501502
...verificationPayload,
502503
stringifiedArtifactJson: artifactString,
503504
instanceAddress: address,
504-
contractClassId: dbContractInstance.currentContractClassId,
505+
// V5 contract addresses stay anchored to the originally deployed class,
506+
// even after an instance updates its current class.
507+
contractClassId: deploymentContractClassId,
505508
immutablesHash: dbContractInstance.immutablesHash,
506509
});
507510
} catch (error) {

0 commit comments

Comments
 (0)