@aztec/l1-artifacts 0.0.1-commit.43597cc1 → 0.0.1-commit.4ad48494d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dest/HonkVerifierBytecode.d.ts +2 -2
  2. package/dest/HonkVerifierBytecode.d.ts.map +1 -1
  3. package/dest/HonkVerifierBytecode.js +1 -1
  4. package/l1-contracts/cache/solidity-files-cache.json +1 -1
  5. package/l1-contracts/generated/HonkVerifier.sol +45 -45
  6. package/l1-contracts/out/DeployAztecL1Contracts.s.sol/DeployAztecL1Contracts.json +1 -1
  7. package/l1-contracts/out/DeployAztecL1Contracts.t.sol/DeployAztecL1ContractsTest.json +1 -1
  8. package/l1-contracts/out/DeployRollupForUpgrade.s.sol/DeployRollupForUpgrade.json +1 -1
  9. package/l1-contracts/out/DeployRollupForUpgrade.t.sol/DeployRollupForUpgradeTest.json +1 -1
  10. package/l1-contracts/out/DeployRollupLib.sol/DeployRollupLib.json +1 -1
  11. package/l1-contracts/out/HonkVerifier.sol/BaseHonkVerifier.json +1 -1
  12. package/l1-contracts/out/HonkVerifier.sol/CommitmentSchemeLib.json +1 -1
  13. package/l1-contracts/out/HonkVerifier.sol/FrLib.json +1 -1
  14. package/l1-contracts/out/HonkVerifier.sol/Honk.json +1 -1
  15. package/l1-contracts/out/HonkVerifier.sol/HonkVerificationKey.json +1 -1
  16. package/l1-contracts/out/HonkVerifier.sol/HonkVerifier.json +1 -1
  17. package/l1-contracts/out/HonkVerifier.sol/IVerifier.json +1 -1
  18. package/l1-contracts/out/HonkVerifier.sol/RelationsLib.json +1 -1
  19. package/l1-contracts/out/HonkVerifier.sol/TranscriptLib.json +1 -1
  20. package/l1-contracts/out/build-info/{7e22331c8a6126d3.json → 60b17eac4f939157.json} +1 -1
  21. package/l1-contracts/out/shouting.t.sol/ScreamAndShoutTest.json +1 -1
  22. package/l1-contracts/package.json +20 -0
  23. package/l1-contracts/scripts/forge_broadcast.js +327 -0
  24. package/package.json +1 -1
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@aztec/l1-contracts",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "description": "Aztec contracts for the Ethereum mainnet and testnets",
7
+ "devDependencies": {
8
+ "@openzeppelin/merkle-tree": "^1.0.8",
9
+ "ox": "^0.8.3",
10
+ "solhint": "5.1.0"
11
+ },
12
+ "scripts": {
13
+ "format": "forge fmt",
14
+ "lint": "solhint --config ./.solhint.json \"src/**/*.sol\"",
15
+ "lint:fix": "solhint --config ./.solhint.json --fix --noPrompt \"src/**/*.sol\"; forge fmt",
16
+ "slither": "forge clean && forge build --build-info --skip '*/test/**' --force && slither . --checklist --ignore-compile --show-ignored-findings --config-file ./slither.config.json | tee slither_output.md",
17
+ "slither-has-diff": "./slither_has_diff.sh"
18
+ },
19
+ "packageManager": "yarn@4.5.2"
20
+ }
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+ // Note: this would be .ts but Node.js refuses to load .ts from node_modules.
3
+
4
+ // forge_broadcast.js - Reliable forge script broadcast with retry and timeout.
5
+ //
6
+ // Wraps `forge script` with:
7
+ // 1. --batch-size 8 to prevent forge broadcast hangs (forge bug with large RPC batches)
8
+ // 2. External timeout (forge's --timeout is unreliable for broadcast hangs)
9
+ // 3. Retry with --resume on real chains, or full retry from scratch on anvil
10
+ //
11
+ // Anvil's auto-miner has a race condition where batched transactions can get stranded
12
+ // in the mempool — they arrive after the auto-miner already triggered for the batch,
13
+ // and sit waiting for the next trigger that never comes. Neither evm_mine nor --resume
14
+ // can recover these stuck transactions. Interval mining (--block-time) avoids this issue.
15
+ //
16
+ // On anvil, we work around this by clearing broadcast artifacts and retrying from scratch.
17
+ // On real chains (where this anvil-specific bug doesn't apply), we use --resume.
18
+ //
19
+ // Usage:
20
+ // ./scripts/forge_broadcast.js <forge script args...>
21
+ //
22
+ // Pass the same args you'd pass to `forge script`, WITHOUT --broadcast or --batch-size.
23
+ // The wrapper adds those automatically.
24
+ //
25
+ // Example:
26
+ // ./scripts/forge_broadcast.js script/deploy/Deploy.s.sol:Deploy \
27
+ // --rpc-url "$RPC_URL" --private-key "$KEY" -vvv
28
+ //
29
+ // Environment variables:
30
+ // FORGE_BROADCAST_TIMEOUT - Override timeout per attempt in seconds (auto-detected from chain ID)
31
+ // FORGE_BROADCAST_MAX_RETRIES - Max retries after initial attempt (default: 3)
32
+ //
33
+ // Uses only Node.js built-ins (no external dependencies).
34
+
35
+ import { spawn } from "node:child_process";
36
+ import { rmSync, writeSync } from "node:fs";
37
+
38
+ // Chain IDs for timeout selection.
39
+ const MAINNET_CHAIN_ID = 1;
40
+ const SEPOLIA_CHAIN_ID = 11155111;
41
+
42
+ // Timeout per attempt: 300s for mainnet/sepolia (real chains are slow), 50s for everything else.
43
+ // FORGE_BROADCAST_TIMEOUT env var overrides the auto-detected value.
44
+ function getDefaultTimeout(chainId) {
45
+ if (chainId === MAINNET_CHAIN_ID || chainId === SEPOLIA_CHAIN_ID) return 300;
46
+ return 50;
47
+ }
48
+
49
+ const MAX_RETRIES = parseInt(
50
+ process.env.FORGE_BROADCAST_MAX_RETRIES ?? "3",
51
+ 10,
52
+ );
53
+
54
+ if (!Number.isSafeInteger(MAX_RETRIES)) {
55
+ process.stderr.write(`MAX_RETRIES is not a valid integer.\n`);
56
+ process.exit(1);
57
+ }
58
+
59
+ // Batch size of 8 prevents forge from hanging during broadcast.
60
+ // See: https://github.com/foundry-rs/foundry/issues/6796
61
+ const BATCH_SIZE = 8;
62
+ const KILL_GRACE = 15_000;
63
+ // Exit code indicating a timeout, matching the `timeout` coreutil convention.
64
+ const EXIT_TIMEOUT = 124;
65
+ // Delay before retry to let pending transactions settle in the mempool.
66
+ const RETRY_DELAY = 10_000;
67
+
68
+ function log(msg) {
69
+ process.stderr.write(`[forge_broadcast] ${msg}\n`);
70
+ }
71
+
72
+ function sleep(ms) {
73
+ return new Promise((resolve) => setTimeout(resolve, ms));
74
+ }
75
+
76
+ /** Extract --rpc-url value from forge args. */
77
+ function extractRpcUrl(args) {
78
+ for (let i = 0; i < args.length - 1; i++) {
79
+ if (args[i] === "--rpc-url") return args[i + 1];
80
+ }
81
+ return undefined;
82
+ }
83
+
84
+ /** Strip --verify from args, returning the filtered args and whether --verify was present. */
85
+ function extractVerifyFlag(args) {
86
+ const filtered = args.filter((a) => a !== "--verify");
87
+ return { args: filtered, verify: filtered.length !== args.length };
88
+ }
89
+
90
+ const RPC_TIMEOUT = 10_000;
91
+
92
+ /** JSON-RPC call using fetch. Rejects on JSON-RPC errors and timeouts. */
93
+ async function rpcCall(rpcUrl, method, params) {
94
+ const body = JSON.stringify({ jsonrpc: "2.0", id: 1, method, params });
95
+ const res = await fetch(rpcUrl, {
96
+ method: "POST",
97
+ headers: { "Content-Type": "application/json" },
98
+ body,
99
+ signal: AbortSignal.timeout(RPC_TIMEOUT),
100
+ });
101
+ if (!res.ok) {
102
+ throw new Error(`RPC HTTP ${res.status} for ${method}`);
103
+ }
104
+ const data = await res.text();
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(data);
108
+ } catch {
109
+ throw new Error(`Bad RPC response for ${method}: ${data.slice(0, 200)}`);
110
+ }
111
+ if (parsed.error) {
112
+ throw new Error(`RPC error for ${method}: ${JSON.stringify(parsed.error)}`);
113
+ }
114
+ return parsed.result;
115
+ }
116
+
117
+ /** Detect if the RPC endpoint is an anvil dev node via web3_clientVersion. */
118
+ async function detectAnvil(rpcUrl) {
119
+ try {
120
+ const version = await rpcCall(rpcUrl, "web3_clientVersion", []);
121
+ return version.toLowerCase().includes("anvil");
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+
127
+ /** Get the chain ID from the RPC endpoint. */
128
+ async function getChainId(rpcUrl) {
129
+ try {
130
+ const result = await rpcCall(rpcUrl, "eth_chainId", []);
131
+ return parseInt(result, 16);
132
+ } catch {
133
+ return undefined;
134
+ }
135
+ }
136
+
137
+ function runForge(args, timeoutSecs) {
138
+ return new Promise((resolve) => {
139
+ const proc = spawn(
140
+ "forge",
141
+ ["script", ...args, "--broadcast", "--batch-size", String(BATCH_SIZE)],
142
+ {
143
+ stdio: ["ignore", "pipe", "inherit"], // buffer stdout, pass stderr through
144
+ },
145
+ );
146
+
147
+ const stdout = [];
148
+ proc.stdout.on("data", (chunk) => stdout.push(chunk));
149
+
150
+ let timedOut = false;
151
+ let settled = false;
152
+ let killTimer;
153
+
154
+ const timer = setTimeout(() => {
155
+ timedOut = true;
156
+ proc.kill("SIGTERM");
157
+ killTimer = setTimeout(() => proc.kill("SIGKILL"), KILL_GRACE);
158
+ }, timeoutSecs * 1000);
159
+
160
+ const finish = (code) => {
161
+ if (settled) return;
162
+ settled = true;
163
+ clearTimeout(timer);
164
+ clearTimeout(killTimer);
165
+ resolve({ exitCode: timedOut ? EXIT_TIMEOUT : code, stdout });
166
+ };
167
+
168
+ proc.on("error", () => finish(1));
169
+ proc.on("close", (code) => finish(code ?? 1));
170
+ });
171
+ }
172
+
173
+ // Main
174
+
175
+ // Strip --verify from args so it doesn't run during broadcast attempts. Verification
176
+ // happens after all receipts are collected (foundry-rs/foundry crates/script/src/lib.rs:333-338)
177
+ // and forge exits non-zero if ANY verification fails (crates/script/src/verify.rs), even when
178
+ // all transactions landed. We run verification as a separate step after broadcast succeeds.
179
+ const { args: forgeArgs, verify: wantsVerify } = extractVerifyFlag(
180
+ process.argv.slice(2),
181
+ );
182
+ const rpcUrl = extractRpcUrl(forgeArgs);
183
+
184
+ // Query chain info from RPC at startup.
185
+ const chainId = rpcUrl ? await getChainId(rpcUrl) : undefined;
186
+ const TIMEOUT = process.env.FORGE_BROADCAST_TIMEOUT
187
+ ? parseInt(process.env.FORGE_BROADCAST_TIMEOUT, 10)
188
+ : getDefaultTimeout(chainId);
189
+
190
+ if (!Number.isSafeInteger(TIMEOUT)) {
191
+ process.stderr.write(`FORGE_BROADCAST_TIMEOUT is not a valid integer.\n`);
192
+ process.exit(1);
193
+ }
194
+
195
+ log(
196
+ `chain_id=${chainId ?? "unknown"}, timeout=${TIMEOUT}s, max_retries=${MAX_RETRIES}, batch_size=${BATCH_SIZE}${wantsVerify ? ", verify=true (after broadcast)" : ""}`,
197
+ );
198
+
199
+ // Detect anvil once at startup. On anvil, retries reset the chain and start from scratch
200
+ // instead of using --resume, because anvil's auto-miner can strand transactions in the
201
+ // mempool in an unrecoverable state (neither evm_mine nor --resume can flush them).
202
+ const isAnvil = rpcUrl ? await detectAnvil(rpcUrl) : false;
203
+ if (isAnvil) {
204
+ log("Detected anvil — retries will reset chain instead of using --resume.");
205
+ }
206
+
207
+ /**
208
+ * Run contract verification via `forge script --resume --verify --broadcast` (no timeout).
209
+ * Verification uses broadcast artifacts + re-compilation — it doesn't need simulation data.
210
+ * See: foundry-rs/foundry crates/script/src/build.rs (CompiledState::resume) and
211
+ * crates/script/src/verify.rs (verify_contracts).
212
+ * Failure is logged but doesn't affect the exit code — transactions already landed.
213
+ */
214
+ async function runVerification(args) {
215
+ log("Running contract verification (no timeout)...");
216
+ const verifyResult = await new Promise((resolve) => {
217
+ const proc = spawn(
218
+ "forge",
219
+ ["script", ...args, "--broadcast", "--resume", "--verify"],
220
+ {
221
+ stdio: ["ignore", "inherit", "inherit"],
222
+ },
223
+ );
224
+ let settled = false;
225
+ proc.on("error", () => {
226
+ if (!settled) {
227
+ settled = true;
228
+ resolve(1);
229
+ }
230
+ });
231
+ proc.on("close", (code) => {
232
+ if (!settled) {
233
+ settled = true;
234
+ resolve(code ?? 1);
235
+ }
236
+ });
237
+ });
238
+ if (verifyResult === 0) {
239
+ log("Contract verification succeeded.");
240
+ } else {
241
+ log(
242
+ `Contract verification failed (exit ${verifyResult}). Transactions are on-chain; verify manually if needed.`,
243
+ );
244
+ }
245
+ }
246
+
247
+ /** Write buffered stdout to fd 1 (synchronous) and exit. */
248
+ function emitAndExit(result, code) {
249
+ const data = Buffer.concat(result.stdout);
250
+ if (data.length > 0) {
251
+ writeSync(1, data);
252
+ }
253
+ process.exit(code);
254
+ }
255
+
256
+ /** Run verification if requested, then emit stdout and exit. */
257
+ async function verifyAndExit(result) {
258
+ if (wantsVerify) {
259
+ await runVerification(forgeArgs);
260
+ }
261
+ emitAndExit(result, 0);
262
+ }
263
+
264
+ // Attempt 1: initial broadcast
265
+ log(`Attempt 1/${MAX_RETRIES + 1}: broadcasting...`);
266
+ let result = await runForge(forgeArgs, TIMEOUT);
267
+
268
+ if (result.exitCode === 0) {
269
+ log("Broadcast succeeded on first attempt.");
270
+ await verifyAndExit(result);
271
+ }
272
+
273
+ log(
274
+ `Attempt 1 ${result.exitCode === EXIT_TIMEOUT ? `timed out after ${TIMEOUT}s` : `failed (exit ${result.exitCode})`}.`,
275
+ );
276
+
277
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
278
+ log(`Waiting ${RETRY_DELAY / 1000}s before retry...`);
279
+ await sleep(RETRY_DELAY);
280
+
281
+ if (isAnvil) {
282
+ // On anvil: retry from scratch instead of --resume.
283
+ //
284
+ // Anvil's auto-miner has a race condition where batched transactions can arrive
285
+ // after the auto-miner already triggered, stranding them in the mempool. --resume
286
+ // just waits for these same stuck transactions and hangs again. A fresh retry
287
+ // re-simulates from current chain state and re-sends, which works because:
288
+ // - Forge computes new nonces from on-chain state
289
+ // - New transactions replace any stuck ones with the same nonce
290
+ // - The race condition is intermittent (~0.04%), so retries almost always succeed
291
+ rmSync("broadcast", { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
292
+
293
+ log(
294
+ `Attempt ${attempt + 1}/${MAX_RETRIES + 1}: retrying from scratch (anvil)...`,
295
+ );
296
+ result = await runForge(forgeArgs, TIMEOUT);
297
+ } else {
298
+ // On real chains: use --resume to pick up unmined transactions.
299
+ // --resume re-reads broadcast artifacts and resubmits unmined transactions.
300
+ // NOTE: --resume skips simulation, so console.log output (e.g. JSON deploy results)
301
+ // is only produced on the first attempt. We keep the first attempt's stdout (`result`)
302
+ // and only check the exit code from the --resume attempt.
303
+ log(`Attempt ${attempt + 1}/${MAX_RETRIES + 1}: --resume`);
304
+ const resumeResult = await runForge([...forgeArgs, "--resume"], TIMEOUT);
305
+
306
+ if (resumeResult.exitCode === 0) {
307
+ log(`Broadcast succeeded on attempt ${attempt + 1}.`);
308
+ // Emit the first attempt's stdout which has the JSON simulation output.
309
+ await verifyAndExit(result);
310
+ }
311
+ log(
312
+ `Attempt ${attempt + 1} ${resumeResult.exitCode === EXIT_TIMEOUT ? `timed out after ${TIMEOUT}s` : `failed (exit ${resumeResult.exitCode})`}.`,
313
+ );
314
+ continue;
315
+ }
316
+
317
+ if (result.exitCode === 0) {
318
+ log(`Broadcast succeeded on attempt ${attempt + 1}.`);
319
+ await verifyAndExit(result);
320
+ }
321
+ log(
322
+ `Attempt ${attempt + 1} ${result.exitCode === EXIT_TIMEOUT ? `timed out after ${TIMEOUT}s` : `failed (exit ${result.exitCode})`}.`,
323
+ );
324
+ }
325
+
326
+ log(`All ${MAX_RETRIES + 1} attempts failed.`);
327
+ emitAndExit(result, result.exitCode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/l1-artifacts",
3
- "version": "0.0.1-commit.43597cc1",
3
+ "version": "0.0.1-commit.4ad48494d",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./*": "./dest/*.js",