From d1b42fb58625b029ccdd62dccb05afcc618659a7 Mon Sep 17 00:00:00 2001 From: ludamad <163993+ludamad@users.noreply.github.com> Date: Tue, 6 Jan 2026 01:12:54 +0000 Subject: [PATCH] chore: migrate foundry deploy artifacts to yarn-project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Forward l1-contracts through yarn-project so that they can be used in a dockerless npm install Co-authored-by: Alex Gherghisan Co-authored-by: Santiago Palladino Co-authored-by: Thunkar <5404052+Thunkar@users.noreply.github.com> Co-authored-by: iAmMichaelConnor Co-authored-by: mverzilli <651693+mverzilli@users.noreply.github.com> Co-authored-by: Álvaro Rodríguez --- release-image/Dockerfile | 17 +---- release-image/Dockerfile.dockerignore | 33 +-------- .../ethereum/src/deploy_aztec_l1_contracts.ts | 73 +++++++++++++++---- yarn-project/l1-artifacts/.gitignore | 1 + yarn-project/l1-artifacts/package.json | 6 +- .../scripts/copy-foundry-artifacts.sh | 29 ++++++++ 6 files changed, 100 insertions(+), 59 deletions(-) create mode 100755 yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh diff --git a/release-image/Dockerfile b/release-image/Dockerfile index a73555af27a0..d422ccf0a5ed 100644 --- a/release-image/Dockerfile +++ b/release-image/Dockerfile @@ -3,19 +3,10 @@ FROM aztecprotocol/release-image-base # Copy in project files. COPY . /usr/src -# We do a short (~3 second) forge build of 3 stubborn files to make sure the cache has all entries. -# This saves time when users make a local network. -# This is impossibly finicky to get right from outside the container, due to having a different set of files. -RUN cd /usr/src/l1-contracts && \ - forge build && \ - # Remove test files from the cache that cause warnings. - cache="cache/solidity-files-cache.json" && \ - jq '.files |= with_entries(select(.key | endswith(".t.sol") | not))' "$cache" > "$cache.tmp" && mv "$cache.tmp" "$cache" - -# Even after that, we want to make sure that forge COULD force a rebuild for FOUNDRY_PROFILE=production. -# This is used in the prod deploy. As well, --broadcast writes to l1-contracts/broadcast. -RUN mkdir -p /usr/src/l1-contracts/broadcast && \ - chmod -R a+rwX /usr/src/l1-contracts/out /usr/src/l1-contracts/cache /usr/src/l1-contracts/broadcast +# L1 contracts artifacts are pre-built in yarn-project/l1-artifacts/foundry-artifacts. +# Ensure directories are writable for forge --broadcast at runtime. +RUN mkdir -p /usr/src/yarn-project/l1-artifacts/foundry-artifacts/broadcast && \ + chmod -R a+rwX /usr/src/yarn-project/l1-artifacts/foundry-artifacts ARG BUILD_METADATA="" ENV BUILD_METADATA=$BUILD_METADATA diff --git a/release-image/Dockerfile.dockerignore b/release-image/Dockerfile.dockerignore index 233c7bfd5640..abf209ea8d60 100644 --- a/release-image/Dockerfile.dockerignore +++ b/release-image/Dockerfile.dockerignore @@ -19,34 +19,5 @@ !/yarn-project/protocol-contracts/artifacts/ !/yarn-project/noir-contracts.js/artifacts/ !/yarn-project/simulator/artifacts/ - -### Start foundry / l1-contracts files - -# There's a bunch of complexity here because foundry is very particular. -# We want to take our full build from the repo, copy it over, and avoid any -# recompilation or warnings from forge inside the docker container. - -# We need to to run l1-contracts deploy scripts (script/deploy/*) -# All source/test files needed so forge cache entries match and avoid recompilation -!/l1-contracts/foundry.toml -# We have to include the solc binary annoyingly, even if we aim to fully have a cached build. -!/l1-contracts/solc-* -!/l1-contracts/script/deploy -!/l1-contracts/src/ - -# Needed to prevent rebuilds. -!/l1-contracts/cache/ -!/l1-contracts/out/ - -# Remove test files, except for shouting.t.sol that for an undiagnosed reason causes a warning if not present. -/l1-contracts/out/**/*.t.sol -!/l1-contracts/test/shouting.t.sol - -# Honk verifier. -!/l1-contracts/generated/ - -# The minimum needed to run our solidity dependencies. -!/l1-contracts/lib/**/remappings.txt -!/l1-contracts/lib/**/foundry.toml -!/l1-contracts/lib/**/*.sol -### End foundry / l1-contracts files +# This includes all files needed for l1-contracts deployment via forge script. +!/yarn-project/l1-artifacts/l1-contracts/ diff --git a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts index f346244c8cd2..41b1596862a8 100644 --- a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts @@ -10,7 +10,8 @@ import { fileURLToPath } from '@aztec/foundation/url'; import { bn254 } from '@noble/curves/bn254'; import type { Abi, Narrow } from 'abitype'; import { spawn } from 'child_process'; -import { dirname, resolve } from 'path'; +import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from 'fs'; +import { dirname, join, resolve } from 'path'; import readline from 'readline'; import type { Hex } from 'viem'; import { foundry, mainnet, sepolia } from 'viem/chains'; @@ -107,17 +108,66 @@ export interface ValidatorJson { } /** - * Gets the path to the l1-contracts directory. + * Gets the path to the l1-contracts foundry artifacts directory. + * These are copied from l1-contracts to yarn-project/l1-artifacts/l1-contracts + * during build to make yarn-project self-contained. */ export function getL1ContractsPath(): string { - // Try to find l1-contracts relative to this file const currentDir = dirname(fileURLToPath(import.meta.url)); - - // Go up from yarn-project/ethereum/src to yarn-project, then to repo root, then to l1-contracts - const l1ContractsPath = resolve(currentDir, '..', '..', '..', 'l1-contracts'); + // Go up from yarn-project/ethereum/dest to yarn-project, then to l1-artifacts/l1-contracts + const l1ContractsPath = resolve(currentDir, '..', '..', 'l1-artifacts', 'l1-contracts'); return l1ContractsPath; } +// Cached deployment directory +let preparedDeployDir: string | undefined; + +function cleanupDeployDir() { + if (preparedDeployDir) { + try { + rmSync(preparedDeployDir, { recursive: true, force: true }); + } catch { + // ignore cleanup errors + } + preparedDeployDir = undefined; + } +} + +/** + * Prepares a temp directory for forge deployment. + * Copies all artifacts with preserved timestamps (required for forge cache validity). + * A fresh broadcast/ directory is created for deployment outputs. + */ +export function prepareL1ContractsForDeployment(): string { + if (preparedDeployDir && existsSync(preparedDeployDir)) { + return preparedDeployDir; + } + + const basePath = getL1ContractsPath(); + const tempDir = mkdtempSync(join(dirname(basePath), '.foundry-deploy-')); + preparedDeployDir = tempDir; + process.on('exit', cleanupDeployDir); + + // Copy all dirs with preserved timestamps (required for forge cache validity) + const copyOpts = { recursive: true, preserveTimestamps: true }; + cpSync(join(basePath, 'out'), join(tempDir, 'out'), copyOpts); + cpSync(join(basePath, 'lib'), join(tempDir, 'lib'), copyOpts); + cpSync(join(basePath, 'cache'), join(tempDir, 'cache'), copyOpts); + cpSync(join(basePath, 'src'), join(tempDir, 'src'), copyOpts); + cpSync(join(basePath, 'script'), join(tempDir, 'script'), copyOpts); + cpSync(join(basePath, 'generated'), join(tempDir, 'generated'), copyOpts); + cpSync(join(basePath, 'foundry.toml'), join(tempDir, 'foundry.toml')); + cpSync(join(basePath, 'foundry.lock'), join(tempDir, 'foundry.lock')); + for (const file of readdirSync(basePath)) { + if (file.startsWith('solc-')) { + cpSync(join(basePath, file), join(tempDir, file)); + } + } + + mkdirSync(join(tempDir, 'broadcast')); + return tempDir; +} + /** * Computes the validator data for passing to Solidity. * Only computes the G2 public key (which requires scalar multiplication on G2, not available in EVM). @@ -211,7 +261,6 @@ export async function deployAztecL1Contracts( 'Initial validator funding requires minting tokens, which is not possible with an external token.', ); } - const currentDir = dirname(fileURLToPath(import.meta.url)); const chain = createEthereumChain([rpcUrl], chainId); const l1Client = createExtendedL1Client([rpcUrl], privateKey, chain.chainInfo); @@ -240,8 +289,8 @@ export async function deployAztecL1Contracts( } } - // Relative location of l1-contracts in monorepo or docker image. - const l1ContractsPath = resolve(currentDir, '..', '..', '..', 'l1-contracts'); + // Use foundry-artifacts from l1-artifacts package + const l1ContractsPath = prepareL1ContractsForDeployment(); const FORGE_SCRIPT = 'script/deploy/DeployAztecL1Contracts.s.sol'; await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId); @@ -513,10 +562,8 @@ export const deployRollupForUpgrade = async ( | 'zkPassportArgs' >, ) => { - const currentDir = dirname(fileURLToPath(import.meta.url)); - - // Relative location of l1-contracts in monorepo or docker image. - const l1ContractsPath = resolve(currentDir, '..', '..', '..', 'l1-contracts'); + // Use foundry-artifacts from l1-artifacts package + const l1ContractsPath = prepareL1ContractsForDeployment(); const FORGE_SCRIPT = 'script/deploy/DeployRollupForUpgrade.s.sol'; await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId); diff --git a/yarn-project/l1-artifacts/.gitignore b/yarn-project/l1-artifacts/.gitignore index 85de9cf93344..918cd544d5f1 100644 --- a/yarn-project/l1-artifacts/.gitignore +++ b/yarn-project/l1-artifacts/.gitignore @@ -1 +1,2 @@ src +l1-contracts diff --git a/yarn-project/l1-artifacts/package.json b/yarn-project/l1-artifacts/package.json index 2ebf2418040d..b37e4cef8d98 100644 --- a/yarn-project/l1-artifacts/package.json +++ b/yarn-project/l1-artifacts/package.json @@ -16,7 +16,8 @@ "clean": "rm -rf ./dest ./generated .tsbuildinfo", "formatting": "run -T prettier --check ./generated && run -T eslint ./generated", "formatting:fix": "run -T prettier -w ./generated", - "generate": "yarn generate:l1-contracts", + "copy-artifacts": "bash scripts/copy-foundry-artifacts.sh", + "generate": "yarn copy-artifacts && yarn generate:l1-contracts", "generate:l1-contracts": "bash scripts/generate-artifacts.sh" }, "dependencies": { @@ -30,7 +31,8 @@ }, "files": [ "dest", - "generated" + "generated", + "l1-contracts" ], "types": "./dest/index.d.ts" } diff --git a/yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh b/yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh new file mode 100755 index 000000000000..f9f909e805f1 --- /dev/null +++ b/yarn-project/l1-artifacts/scripts/copy-foundry-artifacts.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Copies select Foundry artifacts from l1-contracts to yarn-project/l1-artifacts/l1-contracts. +# This makes yarn-project self-contained for runtime contract deployment. +# +# We use cp -p to preserve timestamps - forge cache uses timestamps to detect changes. +# See release-image/Dockerfile.dockerignore for the canonical list of what's needed. + +cd $(git rev-parse --show-toplevel)/yarn-project/l1-artifacts + +src="../../l1-contracts" + +[ -d "$src/out" ] || { echo "Error: l1-contracts/out not found. Build l1-contracts first."; exit 1; } + +rm -rf "l1-contracts" +mkdir -p "l1-contracts/script" "l1-contracts/lib" "l1-contracts/broadcast" + +# Copy build artifacts, cache, sources, and config (preserving timestamps for cache validity) +cp -rp "$src"/{out,cache,src,generated} "l1-contracts/" +cp -rp "$src/script/deploy" "l1-contracts/script/" # only deploy/, other scripts depend on test files +cp -p "$src"/{foundry.toml,foundry.lock,solc-*} "l1-contracts/" +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" {} +) + +# Foundry is very finicky about copying out subsets. +# Patch over what foundry feels needs to be rebuild (~3 seconds on mainframe) +(cd "l1-contracts" && forge build)