Skip to content

Foundry viaIR vs solc Standard JSON Bytecode Mismatch #12737

@jolestar

Description

@jolestar

Component

Forge

Have you ensured that all of these are up to date?

  • Foundry
  • Foundryup

What version of Foundry are you on?

forge 1.4.3-stable (fa9f934, 2025-10-22, maxperf)

What version of Foundryup are you on?

foundryup 1.3.0

What command(s) is the bug in?

forge build

Operating System

macOS (Intel)

Describe the bug

Summary

When compiling with viaIR = true and solc 0.8.20+commit.a1b79de6, the bytecode produced by forge build does not match the bytecode produced by running solc --standard-json on the same Standard JSON exported by forge inspect. The metadata hash is identical; only the code layout (jump offsets) differs. This breaks Etherscan/Basescan verification (they recompile with solc and get different bytecode than the on-chain/forge version).

Environment

  • Foundry: latest stable (forge via ~/.foundry/bin, using --use 0.8.20+commit.a1b79de6)
  • solc: official binary 0.8.20+commit.a1b79de6 from binaries.soliditylang.org
  • OS/arch: macOS x86_64
  • Settings: viaIR = true, optimizer = true, runs = 200, evmVersion = shanghai

Minimal Repro

Contract: src/MinimalEIP712.sol (uses OZ EIP712/ECDSA; viaIR on)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract MinimalEIP712 is EIP712 {
    constructor() EIP712("Mini", "1") {}
    function verify(bytes calldata signature) external view returns (address) {
        bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(keccak256("Mail()"))));
        return ECDSA.recover(digest, signature);
    }
}

Steps

  1. Build with forge (viaIR, solc 0.8.20):
forge build --use 0.8.20+commit.a1b79de6 --force
  1. Export Standard JSON:
forge inspect src/MinimalEIP712.sol:MinimalEIP712 standard-json > /tmp/eip_std.json
  1. Compile with official solc:
/tmp/solc-0.8.20-macosx-amd64 --standard-json /tmp/eip_std.json > /tmp/solc_eip_output.json
  1. Extract bytecode and compare:
jq -r '.bytecode.object' out/MinimalEIP712.sol/MinimalEIP712.json > /tmp/forge_eip.hex
tail -c +3 /tmp/forge_eip.hex > /tmp/forge_eip.stripped.hex
jq -r '.contracts["src/MinimalEIP712.sol"].MinimalEIP712.evm.bytecode.object' /tmp/solc_eip_output.json > /tmp/solc_eip.hex

wc -c /tmp/forge_eip.stripped.hex /tmp/solc_eip.hex   # both 6309
cmp -l /tmp/forge_eip.stripped.hex /tmp/solc_eip.hex | head

Observed Difference

  • Lengths match; metadata hash matches; code layout differs.
  • First diff at offset ~510 (chars):
    • forge: ...80518461045f015260a0518461052b0152...
    • solc : ...805184610654015260a051846107200152...
  • More jump offsets differ downstream.
  • On-chain bytecode (built/deployed by forge) matches the forge artifact, but not the solc recompilation of the forge-exported Standard JSON.

Control Case

  • src/MinimalViaIR.sol (simple return 42) under the same procedure shows no diff. The diff appears on this EIP712 example and on the larger X402X contract.

Expected

Bytecode produced by forge build should be identical to the bytecode produced by running official solc on the forge-exported Standard JSON (same version/settings).

Impact

Etherscan/Basescan recompile with solc and get different bytecode than what forge produced/deployed, so verification fails even though metadata is identical.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions