-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
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 (
forgevia~/.foundry/bin, using--use 0.8.20+commit.a1b79de6) - solc: official binary
0.8.20+commit.a1b79de6frombinaries.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
- Build with forge (viaIR, solc 0.8.20):
forge build --use 0.8.20+commit.a1b79de6 --force- Export Standard JSON:
forge inspect src/MinimalEIP712.sol:MinimalEIP712 standard-json > /tmp/eip_std.json- Compile with official solc:
/tmp/solc-0.8.20-macosx-amd64 --standard-json /tmp/eip_std.json > /tmp/solc_eip_output.json- 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 | headObserved Difference
- Lengths match; metadata hash matches; code layout differs.
- First diff at offset ~510 (chars):
- forge:
...80518461045f015260a0518461052b0152... - solc :
...805184610654015260a051846107200152...
- forge:
- 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(simplereturn 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
Labels
Type
Projects
Status