diff --git a/l1-contracts/package.json b/l1-contracts/package.json index 8d7e6e085142..f7c83b6017d4 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -1,6 +1,7 @@ { "name": "@aztec/l1-contracts", "version": "0.1.0", + "type": "module", "license": "Apache-2.0", "description": "Aztec contracts for the Ethereum mainnet and testnets", "devDependencies": { diff --git a/l1-contracts/scripts/forge_broadcast.js b/l1-contracts/scripts/forge_broadcast.js new file mode 100755 index 000000000000..d8df4462617a --- /dev/null +++ b/l1-contracts/scripts/forge_broadcast.js @@ -0,0 +1,340 @@ +#!/usr/bin/env node +// Note: this would be .ts but Node.js refuses to load .ts from node_modules. + +// forge_broadcast.js - Reliable forge script broadcast with retry and timeout. +// +// Wraps `forge script` with: +// 1. --batch-size 8 to prevent forge broadcast hangs (forge bug with large RPC batches) +// 2. External timeout (forge's --timeout is unreliable for broadcast hangs) +// 3. Retry with --resume on real chains, or full retry from scratch on anvil +// +// Anvil's auto-miner has a race condition where batched transactions can get stranded +// in the mempool — they arrive after the auto-miner already triggered for the batch, +// and sit waiting for the next trigger that never comes. Neither evm_mine nor --resume +// can recover these stuck transactions. Interval mining (--block-time) avoids this issue. +// +// On anvil, we work around this by clearing broadcast artifacts and retrying from scratch. +// On real chains (where this anvil-specific bug doesn't apply), we use --resume. +// +// Usage: +// ./scripts/forge_broadcast.js +// +// Pass the same args you'd pass to `forge script`, WITHOUT --broadcast or --batch-size. +// The wrapper adds those automatically. +// +// Example: +// ./scripts/forge_broadcast.js script/deploy/Deploy.s.sol:Deploy \ +// --rpc-url "$RPC_URL" --private-key "$KEY" -vvv +// +// Environment variables: +// FORGE_BROADCAST_TIMEOUT - Override timeout per attempt in seconds (auto-detected from chain ID) +// FORGE_BROADCAST_MAX_RETRIES - Max retries after initial attempt (default: 3) +// +// Uses only Node.js built-ins (no external dependencies). + +import { spawn } from "node:child_process"; +import { rmSync, writeSync } from "node:fs"; +import { request as httpRequest } from "node:http"; +import { request as httpsRequest } from "node:https"; + +// Chain IDs for timeout selection. +const MAINNET_CHAIN_ID = 1; +const SEPOLIA_CHAIN_ID = 11155111; + +// Timeout per attempt: 300s for mainnet/sepolia (real chains are slow), 50s for everything else. +// FORGE_BROADCAST_TIMEOUT env var overrides the auto-detected value. +function getDefaultTimeout(chainId) { + if (chainId === MAINNET_CHAIN_ID || chainId === SEPOLIA_CHAIN_ID) return 300; + return 50; +} + +const MAX_RETRIES = parseInt( + process.env.FORGE_BROADCAST_MAX_RETRIES ?? "3", + 10, +); + +// Batch size of 8 prevents forge from hanging during broadcast. +// See: https://github.com/foundry-rs/foundry/issues/6796 +const BATCH_SIZE = 8; +const KILL_GRACE = 15_000; +// Exit code indicating a timeout, matching the `timeout` coreutil convention. +const EXIT_TIMEOUT = 124; +// Delay before retry to let pending transactions settle in the mempool. +const RETRY_DELAY = 10_000; + +function log(msg) { + process.stderr.write(`[forge_broadcast] ${msg}\n`); +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** Extract --rpc-url value from forge args. */ +function extractRpcUrl(args) { + for (let i = 0; i < args.length - 1; i++) { + if (args[i] === "--rpc-url") return args[i + 1]; + } + return undefined; +} + +/** Strip --verify from args, returning the filtered args and whether --verify was present. */ +function extractVerifyFlag(args) { + const filtered = args.filter((a) => a !== "--verify"); + return { args: filtered, verify: filtered.length !== args.length }; +} + +const RPC_TIMEOUT = 10_000; + +/** JSON-RPC call using Node.js built-ins. Rejects on JSON-RPC errors and timeouts. */ +function rpcCall(rpcUrl, method, params) { + return new Promise((resolve, reject) => { + const url = new URL(rpcUrl); + const body = JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }); + const reqFn = url.protocol === "https:" ? httpsRequest : httpRequest; + + const timer = setTimeout(() => { + req.destroy(); + reject(new Error(`RPC call ${method} timed out after ${RPC_TIMEOUT}ms`)); + }, RPC_TIMEOUT); + + const req = reqFn( + url, + { method: "POST", headers: { "Content-Type": "application/json" } }, + (res) => { + let data = ""; + res.on("data", (chunk) => (data += chunk)); + res.on("end", () => { + clearTimeout(timer); + try { + const parsed = JSON.parse(data); + if (parsed.error) { + reject( + new Error( + `RPC error for ${method}: ${JSON.stringify(parsed.error)}`, + ), + ); + } else { + resolve(parsed.result); + } + } catch { + reject(new Error(`Bad RPC response: ${data.slice(0, 200)}`)); + } + }); + }, + ); + req.on("error", (err) => { + clearTimeout(timer); + reject(err); + }); + req.write(body); + req.end(); + }); +} + +/** Detect if the RPC endpoint is an anvil dev node via web3_clientVersion. */ +async function detectAnvil(rpcUrl) { + try { + const version = await rpcCall(rpcUrl, "web3_clientVersion", []); + return version.toLowerCase().includes("anvil"); + } catch { + return false; + } +} + +/** Get the chain ID from the RPC endpoint. */ +async function getChainId(rpcUrl) { + try { + const result = await rpcCall(rpcUrl, "eth_chainId", []); + return parseInt(result, 16); + } catch { + return undefined; + } +} + +function runForge(args, timeoutSecs) { + return new Promise((resolve) => { + const proc = spawn( + "forge", + ["script", ...args, "--broadcast", "--batch-size", String(BATCH_SIZE)], + { + stdio: ["ignore", "pipe", "inherit"], // buffer stdout, pass stderr through + }, + ); + + const stdout = []; + proc.stdout.on("data", (chunk) => stdout.push(chunk)); + + let timedOut = false; + let settled = false; + let killTimer; + + const timer = setTimeout(() => { + timedOut = true; + proc.kill("SIGTERM"); + killTimer = setTimeout(() => proc.kill("SIGKILL"), KILL_GRACE); + }, timeoutSecs * 1000); + + const finish = (code) => { + if (settled) return; + settled = true; + clearTimeout(timer); + clearTimeout(killTimer); + resolve({ exitCode: timedOut ? EXIT_TIMEOUT : code, stdout }); + }; + + proc.on("error", () => finish(1)); + proc.on("close", (code) => finish(code ?? 1)); + }); +} + +// Main + +// Strip --verify from args so it doesn't run during broadcast attempts. Verification +// happens after all receipts are collected (foundry-rs/foundry crates/script/src/lib.rs:333-338) +// and forge exits non-zero if ANY verification fails (crates/script/src/verify.rs), even when +// all transactions landed. We run verification as a separate step after broadcast succeeds. +const { args: forgeArgs, verify: wantsVerify } = extractVerifyFlag( + process.argv.slice(2), +); +const rpcUrl = extractRpcUrl(forgeArgs); + +// Query chain info from RPC at startup. +const chainId = rpcUrl ? await getChainId(rpcUrl) : undefined; +const TIMEOUT = process.env.FORGE_BROADCAST_TIMEOUT + ? parseInt(process.env.FORGE_BROADCAST_TIMEOUT, 10) + : getDefaultTimeout(chainId); + +log( + `chain_id=${chainId ?? "unknown"}, timeout=${TIMEOUT}s, max_retries=${MAX_RETRIES}, batch_size=${BATCH_SIZE}${wantsVerify ? ", verify=true (after broadcast)" : ""}`, +); + +// Detect anvil once at startup. On anvil, retries reset the chain and start from scratch +// instead of using --resume, because anvil's auto-miner can strand transactions in the +// mempool in an unrecoverable state (neither evm_mine nor --resume can flush them). +const isAnvil = rpcUrl ? await detectAnvil(rpcUrl) : false; +if (isAnvil) { + log("Detected anvil — retries will reset chain instead of using --resume."); +} + +/** + * Run contract verification via `forge script --resume --verify --broadcast` (no timeout). + * Verification uses broadcast artifacts + re-compilation — it doesn't need simulation data. + * See: foundry-rs/foundry crates/script/src/build.rs (CompiledState::resume) and + * crates/script/src/verify.rs (verify_contracts). + * Failure is logged but doesn't affect the exit code — transactions already landed. + */ +async function runVerification(args) { + log("Running contract verification (no timeout)..."); + const verifyResult = await new Promise((resolve) => { + const proc = spawn( + "forge", + ["script", ...args, "--broadcast", "--resume", "--verify"], + { + stdio: ["ignore", "inherit", "inherit"], + }, + ); + let settled = false; + proc.on("error", () => { + if (!settled) { + settled = true; + resolve(1); + } + }); + proc.on("close", (code) => { + if (!settled) { + settled = true; + resolve(code ?? 1); + } + }); + }); + if (verifyResult === 0) { + log("Contract verification succeeded."); + } else { + log( + `Contract verification failed (exit ${verifyResult}). Transactions are on-chain; verify manually if needed.`, + ); + } +} + +/** Write buffered stdout to fd 1 (synchronous) and exit. */ +function emitAndExit(result, code) { + const data = Buffer.concat(result.stdout); + if (data.length > 0) { + writeSync(1, data); + } + process.exit(code); +} + +/** Run verification if requested, then emit stdout and exit. */ +async function verifyAndExit(result) { + if (wantsVerify) { + await runVerification(forgeArgs); + } + emitAndExit(result, 0); +} + +// Attempt 1: initial broadcast +log(`Attempt 1/${MAX_RETRIES + 1}: broadcasting...`); +let result = await runForge(forgeArgs, TIMEOUT); + +if (result.exitCode === 0) { + log("Broadcast succeeded on first attempt."); + await verifyAndExit(result); +} + +log( + `Attempt 1 ${result.exitCode === EXIT_TIMEOUT ? `timed out after ${TIMEOUT}s` : `failed (exit ${result.exitCode})`}.`, +); + +for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + log(`Waiting ${RETRY_DELAY / 1000}s before retry...`); + await sleep(RETRY_DELAY); + + if (isAnvil) { + // On anvil: retry from scratch instead of --resume. + // + // Anvil's auto-miner has a race condition where batched transactions can arrive + // after the auto-miner already triggered, stranding them in the mempool. --resume + // just waits for these same stuck transactions and hangs again. A fresh retry + // re-simulates from current chain state and re-sends, which works because: + // - Forge computes new nonces from on-chain state + // - New transactions replace any stuck ones with the same nonce + // - The race condition is intermittent (~0.04%), so retries almost always succeed + rmSync("broadcast", { recursive: true, force: true }); + + log( + `Attempt ${attempt + 1}/${MAX_RETRIES + 1}: retrying from scratch (anvil)...`, + ); + result = await runForge(forgeArgs, TIMEOUT); + } else { + // On real chains: use --resume to pick up unmined transactions. + // --resume re-reads broadcast artifacts and resubmits unmined transactions. + // NOTE: --resume skips simulation, so console.log output (e.g. JSON deploy results) + // is only produced on the first attempt. We keep the first attempt's stdout (`result`) + // and only check the exit code from the --resume attempt. + log(`Attempt ${attempt + 1}/${MAX_RETRIES + 1}: --resume`); + const resumeResult = await runForge([...forgeArgs, "--resume"], TIMEOUT); + + if (resumeResult.exitCode === 0) { + log(`Broadcast succeeded on attempt ${attempt + 1}.`); + // Emit the first attempt's stdout which has the JSON simulation output. + await verifyAndExit(result); + } + log( + `Attempt ${attempt + 1} ${resumeResult.exitCode === EXIT_TIMEOUT ? `timed out after ${TIMEOUT}s` : `failed (exit ${resumeResult.exitCode})`}.`, + ); + continue; + } + + if (result.exitCode === 0) { + log(`Broadcast succeeded on attempt ${attempt + 1}.`); + await verifyAndExit(result); + } + log( + `Attempt ${attempt + 1} ${result.exitCode === EXIT_TIMEOUT ? `timed out after ${TIMEOUT}s` : `failed (exit ${result.exitCode})`}.`, + ); +} + +log(`All ${MAX_RETRIES + 1} attempts failed.`); +emitAndExit(result, result.exitCode); diff --git a/l1-contracts/scripts/run_rollup_upgrade.sh b/l1-contracts/scripts/run_rollup_upgrade.sh index f8f25c77f7e1..3cabda3ecf09 100755 --- a/l1-contracts/scripts/run_rollup_upgrade.sh +++ b/l1-contracts/scripts/run_rollup_upgrade.sh @@ -20,10 +20,10 @@ echo "=== Deploying rollup upgrade ===" echo "Registry: $registry_address" REGISTRY_ADDRESS="$registry_address" \ - REAL_VERIFIER="${REAL_VERIFIER:-true}" \ - forge script script/deploy/DeployRollupForUpgrade.s.sol:DeployRollupForUpgrade \ - --rpc-url "$L1_RPC_URL" \ - --private-key "$ROLLUP_DEPLOYMENT_PRIVATE_KEY" \ - --broadcast \ - ${ETHERSCAN_API_KEY:+--verify} \ +REAL_VERIFIER="${REAL_VERIFIER:-true}" \ +./scripts/forge_broadcast.js \ + script/deploy/DeployRollupForUpgrade.s.sol:DeployRollupForUpgrade \ + --rpc-url "$L1_RPC_URL" \ + --private-key "$ROLLUP_DEPLOYMENT_PRIVATE_KEY" \ + ${ETHERSCAN_API_KEY:+--verify} \ -vvv diff --git a/l1-contracts/scripts/test_rollup_upgrade.sh b/l1-contracts/scripts/test_rollup_upgrade.sh index 38e79b46d763..5633984d94b9 100755 --- a/l1-contracts/scripts/test_rollup_upgrade.sh +++ b/l1-contracts/scripts/test_rollup_upgrade.sh @@ -17,19 +17,25 @@ cleanup() { } trap cleanup EXIT -echo "=== Starting anvil ===" -anvil & +# Clean stale broadcast artifacts from previous runs to avoid nonce conflicts. +rm -rf broadcast/ + +# Use a random port to avoid conflicts with other anvil instances. +ANVIL_PORT="${ANVIL_PORT:-$(shuf -i 10000-60000 -n 1)}" + +echo "=== Starting anvil on port $ANVIL_PORT ===" +anvil --port "$ANVIL_PORT" & anvil_pid=$! sleep 2 -export L1_RPC_URL="http://127.0.0.1:8545" +export L1_RPC_URL="http://127.0.0.1:$ANVIL_PORT" export ROLLUP_DEPLOYMENT_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" echo "=== Deploying initial L1 contracts ===" -forge script script/deploy/DeployAztecL1Contracts.s.sol:DeployAztecL1Contracts \ +./scripts/forge_broadcast.js \ + script/deploy/DeployAztecL1Contracts.s.sol:DeployAztecL1Contracts \ --rpc-url "$L1_RPC_URL" \ --private-key "$ROLLUP_DEPLOYMENT_PRIVATE_KEY" \ - --broadcast \ --json > /tmp/initial_deploy.jsonl deploy_json=$(head -1 /tmp/initial_deploy.jsonl | jq -r '.logs[0]' | sed 's/JSON DEPLOY RESULT: //') diff --git a/l1-contracts/yarn.lock b/l1-contracts/yarn.lock index 9c68cb8b8b46..26f8e9c14913 100644 --- a/l1-contracts/yarn.lock +++ b/l1-contracts/yarn.lock @@ -6,9 +6,9 @@ __metadata: cacheKey: 10c0 "@adraffy/ens-normalize@npm:^1.11.0": - version: 1.11.0 - resolution: "@adraffy/ens-normalize@npm:1.11.0" - checksum: 10c0/5111d0f1a273468cb5661ed3cf46ee58de8f32f84e2ebc2365652e66c1ead82649df94c736804e2b9cfa831d30ef24e1cc3575d970dbda583416d3a98d8870a6 + version: 1.11.1 + resolution: "@adraffy/ens-normalize@npm:1.11.1" + checksum: 10c0/b364e2a57131db278ebf2f22d1a1ac6d8aea95c49dd2bbbc1825870b38aa91fd8816aba580a1f84edc50a45eb6389213dacfd1889f32893afc8549a82d304767 languageName: node linkType: hard @@ -23,20 +23,20 @@ __metadata: linkType: soft "@babel/code-frame@npm:^7.0.0": - version: 7.27.1 - resolution: "@babel/code-frame@npm:7.27.1" + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" dependencies: - "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" js-tokens: "npm:^4.0.0" picocolors: "npm:^1.1.1" - checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-identifier@npm:7.27.1" - checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 languageName: node linkType: hard @@ -142,11 +142,11 @@ __metadata: linkType: hard "@noble/curves@npm:^1.9.1, @noble/curves@npm:~1.9.0": - version: 1.9.2 - resolution: "@noble/curves@npm:1.9.2" + version: 1.9.7 + resolution: "@noble/curves@npm:1.9.7" dependencies: "@noble/hashes": "npm:1.8.0" - checksum: 10c0/21d049ae4558beedbf5da0004407b72db84360fa29d64822d82dc9e80251e1ecb46023590cc4b20e70eed697d1b87279b4911dc39f8694c51c874289cfc8e9a7 + checksum: 10c0/150014751ebe8ca06a8654ca2525108452ea9ee0be23430332769f06808cddabfe84f248b6dbf836916bc869c27c2092957eec62c7506d68a1ed0a624017c2a3 languageName: node linkType: hard @@ -190,14 +190,14 @@ __metadata: languageName: node linkType: hard -"@pnpm/npm-conf@npm:^2.1.0": - version: 2.3.1 - resolution: "@pnpm/npm-conf@npm:2.3.1" +"@pnpm/npm-conf@npm:^3.0.2": + version: 3.0.2 + resolution: "@pnpm/npm-conf@npm:3.0.2" dependencies: "@pnpm/config.env-replace": "npm:^1.1.0" "@pnpm/network.ca-file": "npm:^1.0.1" config-chain: "npm:^1.1.11" - checksum: 10c0/778a3a34ff7d6000a2594d2a9821f873f737bc56367865718b2cf0ba5d366e49689efe7975148316d7afd8e6f1dcef7d736fbb6ea7ef55caadd1dc93a36bb302 + checksum: 10c0/50026ae4cac7d5d055d4dd4b2886fbc41964db6179406cf2decf625e7a280fbfffd47380df584c085464deba060101169caca5f79e6a062b6c25b527bf60cb67 languageName: node linkType: hard @@ -265,9 +265,9 @@ __metadata: linkType: hard "@solidity-parser/parser@npm:^0.20.0": - version: 0.20.1 - resolution: "@solidity-parser/parser@npm:0.20.1" - checksum: 10c0/fa08c719bace194cb82be80f0efd9c57863aea831ff587a9268752a84a35f00daa8c28c9f5587c64d5cbb969a98f8df714088acdd581702376e45d48d57ee8af + version: 0.20.2 + resolution: "@solidity-parser/parser@npm:0.20.2" + checksum: 10c0/23b0b7ed343a4fa55cb8621cf4fc81b0bdd791a4ee7e96ced7778db07de66d4e418db2c2dc1525b1f82fc0310ac829c78382327292b37e35cb30a19961fcb429 languageName: node linkType: hard @@ -290,9 +290,9 @@ __metadata: linkType: hard "@types/http-cache-semantics@npm:^4.0.2": - version: 4.0.4 - resolution: "@types/http-cache-semantics@npm:4.0.4" - checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6 + version: 4.2.0 + resolution: "@types/http-cache-semantics@npm:4.2.0" + checksum: 10c0/82dd33cbe7d4843f1e884a251c6a12d385b62274353b9db167462e7fbffdbb3a83606f9952203017c5b8cabbd7b9eef0cf240a3a9dedd20f69875c9701939415 languageName: node linkType: hard @@ -304,17 +304,17 @@ __metadata: linkType: hard "abitype@npm:^1.0.8": - version: 1.0.8 - resolution: "abitype@npm:1.0.8" + version: 1.2.3 + resolution: "abitype@npm:1.2.3" peerDependencies: typescript: ">=5.0.4" - zod: ^3 >=3.22.0 + zod: ^3.22.0 || ^4.0.0 peerDependenciesMeta: typescript: optional: true zod: optional: true - checksum: 10c0/d3393f32898c1f0f6da4eed2561da6830dcd0d5129a160fae9517214236ee6a6c8e5a0380b8b960c5bc1b949320bcbd015ec7f38b5d7444f8f2b854a1b5dd754 + checksum: 10c0/c8740de1ae4961723a153224a52cb9a34a57903fb5c2ad61d5082b0b79b53033c9335381aa8c663c7ec213c9955a9853f694d51e95baceedef27356f7745c634 languageName: node linkType: hard @@ -501,14 +501,14 @@ __metadata: linkType: hard "debug@npm:^4.3.4": - version: 4.4.1 - resolution: "debug@npm:4.4.1" + version: 4.4.3 + resolution: "debug@npm:4.4.3" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 languageName: node linkType: hard @@ -543,11 +543,11 @@ __metadata: linkType: hard "error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" dependencies: is-arrayish: "npm:^0.2.1" - checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 languageName: node linkType: hard @@ -605,9 +605,9 @@ __metadata: linkType: hard "fast-uri@npm:^3.0.1": - version: 3.0.6 - resolution: "fast-uri@npm:3.0.6" - checksum: 10c0/74a513c2af0584448aee71ce56005185f81239eab7a2343110e5bad50c39ad4fb19c5a6f99783ead1cac7ccaf3461a6034fda89fffa2b30b6d99b9f21c2f9d29 + version: 3.1.0 + resolution: "fast-uri@npm:3.1.0" + checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 languageName: node linkType: hard @@ -758,13 +758,13 @@ __metadata: linkType: hard "js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" dependencies: argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 languageName: node linkType: hard @@ -829,9 +829,9 @@ __metadata: linkType: hard "lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + version: 4.17.23 + resolution: "lodash@npm:4.17.23" + checksum: 10c0/1264a90469f5bb95d4739c43eb6277d15b6d9e186df4ac68c3620443160fc669e2f14c11e7d8b2ccf078b81d06147c01a8ccced9aab9f9f63d50dcf8cace6bf6 languageName: node linkType: hard @@ -887,9 +887,9 @@ __metadata: linkType: hard "normalize-url@npm:^8.0.0": - version: 8.0.2 - resolution: "normalize-url@npm:8.0.2" - checksum: 10c0/1c62eee6ce184ad4a463ff2984ce5e440a5058c9dd7c5ef80c0a7696bbb1d3638534e266afb14ef9678dfa07fb6c980ef4cde990c80eeee55900c378b7970584 + version: 8.1.1 + resolution: "normalize-url@npm:8.1.1" + checksum: 10c0/1beb700ce42acb2288f39453cdf8001eead55bbf046d407936a40404af420b8c1c6be97a869884ae9e659d7b1c744e40e905c875ac9290644eec2e3e6fb0b370 languageName: node linkType: hard @@ -903,8 +903,8 @@ __metadata: linkType: hard "ox@npm:^0.8.3": - version: 0.8.3 - resolution: "ox@npm:0.8.3" + version: 0.8.9 + resolution: "ox@npm:0.8.9" dependencies: "@adraffy/ens-normalize": "npm:^1.11.0" "@noble/ciphers": "npm:^1.3.0" @@ -919,7 +919,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/3fd2ef5322c2999331cfe46121b3685615236c30f94ca3d956097126e1ce04758c96a76e75198b6c6e34c4a0c5e2dbae7bfa0224ba7e38b9518558f0a3105123 + checksum: 10c0/d3a0c4e3f908e0d18914f17d9c832e777de6caf7d8395bf35978d9cca196e540fe63c230a40bfe462eb3a320c1aba0749e0bede0a5fc4e8501b20d4e784c64ad languageName: node linkType: hard @@ -1036,11 +1036,11 @@ __metadata: linkType: hard "registry-auth-token@npm:^5.0.1": - version: 5.1.0 - resolution: "registry-auth-token@npm:5.1.0" + version: 5.1.1 + resolution: "registry-auth-token@npm:5.1.1" dependencies: - "@pnpm/npm-conf": "npm:^2.1.0" - checksum: 10c0/316229bd8a4acc29a362a7a3862ff809e608256f0fd9e0b133412b43d6a9ea18743756a0ec5ee1467a5384e1023602b85461b3d88d1336b11879e42f7cf02c12 + "@pnpm/npm-conf": "npm:^3.0.2" + checksum: 10c0/86b0f7fd87d327cb4177fee69bcf96563147ea72e206bc9c7a6a50a51c785a31b83a6c45956a489ed292d23b908b2755a075d0b2f7fec1ba91b1fb800b24cee3 languageName: node linkType: hard @@ -1084,11 +1084,11 @@ __metadata: linkType: hard "semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.4": - version: 7.7.2 - resolution: "semver@npm:7.7.2" + version: 7.7.4 + resolution: "semver@npm:7.7.4" bin: semver: bin/semver.js - checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 languageName: node linkType: hard diff --git a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts index e6a6143c4843..6497f5c0400e 100644 --- a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts @@ -31,9 +31,9 @@ const logger = createLogger('ethereum:deploy_aztec_l1_contracts'); const JSON_DEPLOY_RESULT_PREFIX = 'JSON DEPLOY RESULT:'; /** - * Runs a process with the given command, arguments, and environment. - * If the process outputs a line starting with JSON_DEPLOY_RESULT_PREFIX, - * the JSON is parsed and returned. + * Runs a process and parses JSON deploy results from stdout. + * Lines starting with JSON_DEPLOY_RESULT_PREFIX are parsed and returned. + * All other stdout goes to logger.info, stderr goes to logger.warn. */ function runProcess( command: string, @@ -49,26 +49,41 @@ function runProcess( }); let result: T | undefined; + let parseError: Error | undefined; + let settled = false; readline.createInterface({ input: proc.stdout }).on('line', line => { const trimmedLine = line.trim(); if (trimmedLine.startsWith(JSON_DEPLOY_RESULT_PREFIX)) { const jsonStr = trimmedLine.slice(JSON_DEPLOY_RESULT_PREFIX.length).trim(); - // TODO(AD): should this be a zod parse? - result = JSON.parse(jsonStr); + try { + result = JSON.parse(jsonStr); + } catch { + parseError = new Error(`Failed to parse deploy result JSON: ${jsonStr.slice(0, 200)}`); + } } else { logger.info(line); } }); - readline.createInterface({ input: proc.stderr }).on('line', logger.error.bind(logger)); + readline.createInterface({ input: proc.stderr }).on('line', logger.warn.bind(logger)); proc.on('error', error => { + if (settled) { + return; + } + settled = true; reject(new Error(`Failed to spawn ${command}: ${error.message}`)); }); proc.on('close', code => { + if (settled) { + return; + } + settled = true; if (code !== 0) { - reject(new Error(`${command} exited with code ${code}. See logs for details.\n`)); + reject(new Error(`${command} exited with code ${code}`)); + } else if (parseError) { + reject(parseError); } else { resolve(result); } @@ -321,11 +336,8 @@ export async function deployAztecL1Contracts( ); } - // From heuristic testing. More caused issues with anvil. - const MAGIC_ANVIL_BATCH_SIZE = 8; - // Anvil seems to stall with unbounded batch size. Otherwise no max batch size is desirable. + const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js'); const forgeArgs = [ - 'script', FORGE_SCRIPT, '--sig', 'run()', @@ -333,9 +345,6 @@ export async function deployAztecL1Contracts( privateKey, '--rpc-url', rpcUrl, - '--broadcast', - '--batch-size', - MAGIC_ANVIL_BATCH_SIZE.toString(), ...(shouldVerify ? ['--verify'] : []), ]; const forgeEnv = { @@ -344,7 +353,12 @@ export async function deployAztecL1Contracts( FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined, ...getDeployAztecL1ContractsEnvVars(args), }; - const result = await runProcess('forge', forgeArgs, forgeEnv, l1ContractsPath); + const result = await runProcess( + process.execPath, + [scriptPath, ...forgeArgs], + forgeEnv, + l1ContractsPath, + ); if (!result) { throw new Error('Forge script did not output deployment result'); } @@ -587,17 +601,8 @@ export const deployRollupForUpgrade = async ( const FORGE_SCRIPT = 'script/deploy/DeployRollupForUpgrade.s.sol'; await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId); - const forgeArgs = [ - 'script', - FORGE_SCRIPT, - '--sig', - 'run()', - '--private-key', - privateKey, - '--rpc-url', - rpcUrl, - '--broadcast', - ]; + const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js'); + const forgeArgs = [FORGE_SCRIPT, '--sig', 'run()', '--private-key', privateKey, '--rpc-url', rpcUrl]; const forgeEnv = { FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined, // Env vars required by l1-contracts/script/deploy/RollupConfiguration.sol. @@ -606,7 +611,12 @@ export const deployRollupForUpgrade = async ( ...getDeployRollupForUpgradeEnvVars(args), }; - const result = await runProcess('forge', forgeArgs, forgeEnv, l1ContractsPath); + const result = await runProcess( + process.execPath, + [scriptPath, ...forgeArgs], + forgeEnv, + l1ContractsPath, + ); if (!result) { throw new Error('Forge script did not output deployment result'); } diff --git a/yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh b/yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh index 8607c60dee9b..cb2f03b42cc5 100755 --- a/yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh @@ -23,7 +23,10 @@ cp -rp "$src/script/deploy" "l1-contracts/script/" # only deploy/, other script mkdir -p "l1-contracts/test/script" cp -p "$src/test/shouting.t.sol" "l1-contracts/test/" cp -p "$src"/test/script/*.sol "l1-contracts/test/script/" -cp -p "$src"/{foundry.toml,foundry.lock,solc-*} "l1-contracts/" +cp -p "$src"/{foundry.toml,foundry.lock,package.json,solc-*} "l1-contracts/" +# Copy the forge broadcast wrapper (now a plain .js source file). +mkdir -p "l1-contracts/scripts" +cp -p "$src/scripts/forge_broadcast.js" "l1-contracts/scripts/" abs_dest=$(pwd)/l1-contracts # Keep only the foundry relevant files from lib (cd "$src" && find lib \( -name "*.sol" -o -name "remappings.txt" -o -name "foundry.toml" \) -exec cp --parents -t "$abs_dest" {} +)