Skip to content

fix: match artifact by profile when writing extra output files#350

Merged
zerosnacks merged 12 commits intomainfrom
fix/extra-output-files-multiple-profiles
Jan 20, 2026
Merged

fix: match artifact by profile when writing extra output files#350
zerosnacks merged 12 commits intomainfrom
fix/extra-output-files-multiple-profiles

Conversation

@gakonst
Copy link
Member

@gakonst gakonst commented Jan 20, 2026

Summary

When using forge build --extra-output-files bin with multiple compiler profiles (via additional_compiler_profiles and compilation_restrictions), only a single .bin file was generated instead of one per profile. The .json artifacts were correctly generated per profile (e.g., Counter.json and Counter.optimized.json), but only one .bin file was created, containing bytecode from whichever profile was processed last.

Root Cause

Artifacts::find_artifact() only matches by:

  • file path
  • contract name
  • version

When the same contract is compiled with multiple profiles using the same solc version, find_artifact() returns the first match for all profiles. This causes write_extras() to write all profiles' extra output files to the same path, with later profiles overwriting earlier ones.

Fix

  • Add find_artifact_with_profile() that includes profile matching
  • Update ConfigurableArtifacts::handle_artifacts() to use the new method

This ensures each profile's extra output files are written to the correct path with the profile suffix (e.g., Counter.bin and Counter.optimized.bin).

Expected Behavior After Fix

out/Counter.sol/
├── Counter.bin                 # Default profile bytecode
├── Counter.json                # Default profile artifact
├── Counter.optimized.bin       # Optimized profile bytecode
└── Counter.optimized.json      # Optimized profile artifact

Evidence

Without the fix

CI Run: https://github.com/foundry-rs/compilers/actions/runs/21180139935

test extra_output_files_with_multiple_profiles ... FAILED
assertion failed: expected 2 .bin files (one per profile), got: ["Counter.bin"]
  left: "1"
 right: "2"

Only 1 .bin file was created because find_artifact() returns the same artifact for both profiles (matches by version only), causing the second write to overwrite the first.

With the fix

CI Run: https://github.com/foundry-rs/compilers/actions/runs/21179524355

Test passes - 2 .bin files are created because find_artifact_with_profile() correctly matches by profile.


Fixes: foundry-rs/foundry#13057

When using `--extra-output-files bin` with multiple compiler profiles
(via `additional_compiler_profiles` and `compilation_restrictions`),
only a single `.bin` file was generated instead of one per profile.

The root cause was that `Artifacts::find_artifact()` only matched by
file, contract name, and version, but not by profile. When the same
contract was compiled with multiple profiles (same version), the lookup
would return the first match for all profiles, causing extra output
files to be written to the same path and overwriting each other.

This fix:
- Adds `find_artifact_with_profile()` that includes profile matching
- Updates `ConfigurableArtifacts::handle_artifacts()` to use the new
  method, ensuring each profile's extra output files are written to
  the correct path (e.g., `Counter.bin` and `Counter.optimized-runs.bin`)

Fixes: foundry-rs/foundry#13057
Adds a test that verifies when using --extra-output-files with
multiple compiler profiles, separate .bin files are generated for
each profile (e.g., Counter.bin and Counter.optimized.bin).

This test would fail before the fix because find_artifact() only
matched by version, not profile, causing all profiles to write to
the same .bin file path.
- Remove redundant .clone() on contract_path
- Remove redundant .clone() on id.path (simplified to just collect profiles)
- Inline format args in assert! macro
The test now uses a shared library (Lib.sol) that is imported by both:
- Default.sol (compiles with default profile)
- Optimized.sol (compiles with optimized profile via restriction)

This ensures Lib.sol is compiled with BOTH profiles, which is the
scenario that triggers the bug where only one .bin file was generated.
- Shorten comment to fit line length
- Remove redundant .clone() on id.profile
- Use HashSet<&str> instead of Vec<String> for cleaner code
Changed test to use the same pattern as test_settings_restrictions:
- Default profile uses Paris EVM version
- Cancun profile uses Cancun EVM version
- Cancun.sol uses tstore which requires Cancun EVM version
- This forces Lib.sol to be compiled with both profiles

The optimizer_runs restriction wasn't triggering multi-profile compilation
because the default profile didn't have explicit optimizer_runs set.
Copy the working test setup exactly to ensure the multi-profile
compilation is triggered correctly.
Empty contracts produce no bytecode, so no .bin file is written.
The bug only manifests when:
1. Same solc version
2. Same contract file/name
3. Different profiles (with different settings like optimizer_runs)

The previous test used different EVM versions which caused different
solc versions to be selected, so the version check in find_artifact()
worked correctly and the test passed even without the fix.

This test now:
- Fixes solc to 0.8.26 for both profiles
- Uses optimizer_runs restriction (10000) to force one contract to optimized profile
- Verifies Counter.sol is compiled with same version but different profiles
- Checks that 2 .bin files exist (would be 1 without the fix)
@zerosnacks zerosnacks marked this pull request as ready for review January 20, 2026 17:11
@zerosnacks zerosnacks merged commit 08deeea into main Jan 20, 2026
18 checks passed
@zerosnacks zerosnacks deleted the fix/extra-output-files-multiple-profiles branch January 20, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(forge): with multiple profiles; forge build --extra-output-files bin only produces a single .bin but correctly generates multiple .json artifacts

3 participants