Skip to content

Commit 37c2b1a

Browse files
authored
chore: migrate foundry deploy artifacts to yarn-project (#19312)
Forward l1-contracts through yarn-project so that they can be used in a dockerless npm install
2 parents 72e908d + d1b42fb commit 37c2b1a

File tree

6 files changed

+100
-59
lines changed

6 files changed

+100
-59
lines changed

release-image/Dockerfile

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,10 @@ FROM aztecprotocol/release-image-base
33
# Copy in project files.
44
COPY . /usr/src
55

6-
# We do a short (~3 second) forge build of 3 stubborn files to make sure the cache has all entries.
7-
# This saves time when users make a local network.
8-
# This is impossibly finicky to get right from outside the container, due to having a different set of files.
9-
RUN cd /usr/src/l1-contracts && \
10-
forge build && \
11-
# Remove test files from the cache that cause warnings.
12-
cache="cache/solidity-files-cache.json" && \
13-
jq '.files |= with_entries(select(.key | endswith(".t.sol") | not))' "$cache" > "$cache.tmp" && mv "$cache.tmp" "$cache"
14-
15-
# Even after that, we want to make sure that forge COULD force a rebuild for FOUNDRY_PROFILE=production.
16-
# This is used in the prod deploy. As well, --broadcast writes to l1-contracts/broadcast.
17-
RUN mkdir -p /usr/src/l1-contracts/broadcast && \
18-
chmod -R a+rwX /usr/src/l1-contracts/out /usr/src/l1-contracts/cache /usr/src/l1-contracts/broadcast
6+
# L1 contracts artifacts are pre-built in yarn-project/l1-artifacts/foundry-artifacts.
7+
# Ensure directories are writable for forge --broadcast at runtime.
8+
RUN mkdir -p /usr/src/yarn-project/l1-artifacts/foundry-artifacts/broadcast && \
9+
chmod -R a+rwX /usr/src/yarn-project/l1-artifacts/foundry-artifacts
1910

2011
ARG BUILD_METADATA=""
2112
ENV BUILD_METADATA=$BUILD_METADATA

release-image/Dockerfile.dockerignore

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,5 @@
1919
!/yarn-project/protocol-contracts/artifacts/
2020
!/yarn-project/noir-contracts.js/artifacts/
2121
!/yarn-project/simulator/artifacts/
22-
23-
### Start foundry / l1-contracts files
24-
25-
# There's a bunch of complexity here because foundry is very particular.
26-
# We want to take our full build from the repo, copy it over, and avoid any
27-
# recompilation or warnings from forge inside the docker container.
28-
29-
# We need to to run l1-contracts deploy scripts (script/deploy/*)
30-
# All source/test files needed so forge cache entries match and avoid recompilation
31-
!/l1-contracts/foundry.toml
32-
# We have to include the solc binary annoyingly, even if we aim to fully have a cached build.
33-
!/l1-contracts/solc-*
34-
!/l1-contracts/script/deploy
35-
!/l1-contracts/src/
36-
37-
# Needed to prevent rebuilds.
38-
!/l1-contracts/cache/
39-
!/l1-contracts/out/
40-
41-
# Remove test files, except for shouting.t.sol that for an undiagnosed reason causes a warning if not present.
42-
/l1-contracts/out/**/*.t.sol
43-
!/l1-contracts/test/shouting.t.sol
44-
45-
# Honk verifier.
46-
!/l1-contracts/generated/
47-
48-
# The minimum needed to run our solidity dependencies.
49-
!/l1-contracts/lib/**/remappings.txt
50-
!/l1-contracts/lib/**/foundry.toml
51-
!/l1-contracts/lib/**/*.sol
52-
### End foundry / l1-contracts files
22+
# This includes all files needed for l1-contracts deployment via forge script.
23+
!/yarn-project/l1-artifacts/l1-contracts/

yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import { fileURLToPath } from '@aztec/foundation/url';
1010
import { bn254 } from '@noble/curves/bn254';
1111
import type { Abi, Narrow } from 'abitype';
1212
import { spawn } from 'child_process';
13-
import { dirname, resolve } from 'path';
13+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from 'fs';
14+
import { dirname, join, resolve } from 'path';
1415
import readline from 'readline';
1516
import type { Hex } from 'viem';
1617
import { foundry, mainnet, sepolia } from 'viem/chains';
@@ -107,17 +108,66 @@ export interface ValidatorJson {
107108
}
108109

109110
/**
110-
* Gets the path to the l1-contracts directory.
111+
* Gets the path to the l1-contracts foundry artifacts directory.
112+
* These are copied from l1-contracts to yarn-project/l1-artifacts/l1-contracts
113+
* during build to make yarn-project self-contained.
111114
*/
112115
export function getL1ContractsPath(): string {
113-
// Try to find l1-contracts relative to this file
114116
const currentDir = dirname(fileURLToPath(import.meta.url));
115-
116-
// Go up from yarn-project/ethereum/src to yarn-project, then to repo root, then to l1-contracts
117-
const l1ContractsPath = resolve(currentDir, '..', '..', '..', 'l1-contracts');
117+
// Go up from yarn-project/ethereum/dest to yarn-project, then to l1-artifacts/l1-contracts
118+
const l1ContractsPath = resolve(currentDir, '..', '..', 'l1-artifacts', 'l1-contracts');
118119
return l1ContractsPath;
119120
}
120121

122+
// Cached deployment directory
123+
let preparedDeployDir: string | undefined;
124+
125+
function cleanupDeployDir() {
126+
if (preparedDeployDir) {
127+
try {
128+
rmSync(preparedDeployDir, { recursive: true, force: true });
129+
} catch {
130+
// ignore cleanup errors
131+
}
132+
preparedDeployDir = undefined;
133+
}
134+
}
135+
136+
/**
137+
* Prepares a temp directory for forge deployment.
138+
* Copies all artifacts with preserved timestamps (required for forge cache validity).
139+
* A fresh broadcast/ directory is created for deployment outputs.
140+
*/
141+
export function prepareL1ContractsForDeployment(): string {
142+
if (preparedDeployDir && existsSync(preparedDeployDir)) {
143+
return preparedDeployDir;
144+
}
145+
146+
const basePath = getL1ContractsPath();
147+
const tempDir = mkdtempSync(join(dirname(basePath), '.foundry-deploy-'));
148+
preparedDeployDir = tempDir;
149+
process.on('exit', cleanupDeployDir);
150+
151+
// Copy all dirs with preserved timestamps (required for forge cache validity)
152+
const copyOpts = { recursive: true, preserveTimestamps: true };
153+
cpSync(join(basePath, 'out'), join(tempDir, 'out'), copyOpts);
154+
cpSync(join(basePath, 'lib'), join(tempDir, 'lib'), copyOpts);
155+
cpSync(join(basePath, 'cache'), join(tempDir, 'cache'), copyOpts);
156+
cpSync(join(basePath, 'src'), join(tempDir, 'src'), copyOpts);
157+
cpSync(join(basePath, 'script'), join(tempDir, 'script'), copyOpts);
158+
cpSync(join(basePath, 'generated'), join(tempDir, 'generated'), copyOpts);
159+
cpSync(join(basePath, 'foundry.toml'), join(tempDir, 'foundry.toml'));
160+
cpSync(join(basePath, 'foundry.lock'), join(tempDir, 'foundry.lock'));
161+
for (const file of readdirSync(basePath)) {
162+
if (file.startsWith('solc-')) {
163+
cpSync(join(basePath, file), join(tempDir, file));
164+
}
165+
}
166+
167+
mkdirSync(join(tempDir, 'broadcast'));
168+
return tempDir;
169+
}
170+
121171
/**
122172
* Computes the validator data for passing to Solidity.
123173
* Only computes the G2 public key (which requires scalar multiplication on G2, not available in EVM).
@@ -211,7 +261,6 @@ export async function deployAztecL1Contracts(
211261
'Initial validator funding requires minting tokens, which is not possible with an external token.',
212262
);
213263
}
214-
const currentDir = dirname(fileURLToPath(import.meta.url));
215264
const chain = createEthereumChain([rpcUrl], chainId);
216265

217266
const l1Client = createExtendedL1Client([rpcUrl], privateKey, chain.chainInfo);
@@ -240,8 +289,8 @@ export async function deployAztecL1Contracts(
240289
}
241290
}
242291

243-
// Relative location of l1-contracts in monorepo or docker image.
244-
const l1ContractsPath = resolve(currentDir, '..', '..', '..', 'l1-contracts');
292+
// Use foundry-artifacts from l1-artifacts package
293+
const l1ContractsPath = prepareL1ContractsForDeployment();
245294

246295
const FORGE_SCRIPT = 'script/deploy/DeployAztecL1Contracts.s.sol';
247296
await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
@@ -513,10 +562,8 @@ export const deployRollupForUpgrade = async (
513562
| 'zkPassportArgs'
514563
>,
515564
) => {
516-
const currentDir = dirname(fileURLToPath(import.meta.url));
517-
518-
// Relative location of l1-contracts in monorepo or docker image.
519-
const l1ContractsPath = resolve(currentDir, '..', '..', '..', 'l1-contracts');
565+
// Use foundry-artifacts from l1-artifacts package
566+
const l1ContractsPath = prepareL1ContractsForDeployment();
520567

521568
const FORGE_SCRIPT = 'script/deploy/DeployRollupForUpgrade.s.sol';
522569
await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
src
2+
l1-contracts

yarn-project/l1-artifacts/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"clean": "rm -rf ./dest ./generated .tsbuildinfo",
1717
"formatting": "run -T prettier --check ./generated && run -T eslint ./generated",
1818
"formatting:fix": "run -T prettier -w ./generated",
19-
"generate": "yarn generate:l1-contracts",
19+
"copy-artifacts": "bash scripts/copy-foundry-artifacts.sh",
20+
"generate": "yarn copy-artifacts && yarn generate:l1-contracts",
2021
"generate:l1-contracts": "bash scripts/generate-artifacts.sh"
2122
},
2223
"dependencies": {
@@ -30,7 +31,8 @@
3031
},
3132
"files": [
3233
"dest",
33-
"generated"
34+
"generated",
35+
"l1-contracts"
3436
],
3537
"types": "./dest/index.d.ts"
3638
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Copies select Foundry artifacts from l1-contracts to yarn-project/l1-artifacts/l1-contracts.
5+
# This makes yarn-project self-contained for runtime contract deployment.
6+
#
7+
# We use cp -p to preserve timestamps - forge cache uses timestamps to detect changes.
8+
# See release-image/Dockerfile.dockerignore for the canonical list of what's needed.
9+
10+
cd $(git rev-parse --show-toplevel)/yarn-project/l1-artifacts
11+
12+
src="../../l1-contracts"
13+
14+
[ -d "$src/out" ] || { echo "Error: l1-contracts/out not found. Build l1-contracts first."; exit 1; }
15+
16+
rm -rf "l1-contracts"
17+
mkdir -p "l1-contracts/script" "l1-contracts/lib" "l1-contracts/broadcast"
18+
19+
# Copy build artifacts, cache, sources, and config (preserving timestamps for cache validity)
20+
cp -rp "$src"/{out,cache,src,generated} "l1-contracts/"
21+
cp -rp "$src/script/deploy" "l1-contracts/script/" # only deploy/, other scripts depend on test files
22+
cp -p "$src"/{foundry.toml,foundry.lock,solc-*} "l1-contracts/"
23+
abs_dest=$(pwd)/l1-contracts
24+
# Keep only the foundry relevant files from lib
25+
(cd "$src" && find lib \( -name "*.sol" -o -name "remappings.txt" -o -name "foundry.toml" \) -exec cp --parents -t "$abs_dest" {} +)
26+
27+
# Foundry is very finicky about copying out subsets.
28+
# Patch over what foundry feels needs to be rebuild (~3 seconds on mainframe)
29+
(cd "l1-contracts" && forge build)

0 commit comments

Comments
 (0)