Skip to content

bug(forge): dynamic test linking analysis loads files twice under absolute and relative paths #17219

Description

@decofe

Component

Forge

Describe the bug

With dynamic_test_linking = true, the preprocessor runs an in-process Solar analysis over the compiler input to find tests' bytecode dependencies. On several popular projects this analysis fails, even though the same projects compile without errors with Solar's CLI, forge build --use solar, and solc.

Each failure has the same root cause: the analysis loads the same file twice, under an absolute path and a relative path. Remappings passed to the analysis parser are absolute (forge-std/ → /abs/path/lib/forge-std/src/), while input source keys and relative imports (./Base.sol) use root-relative paths.

Solar's file resolver treats /abs/.../lib/forge-std/src/Base.sol and lib/forge-std/src/Base.sol as two different files. Trace from v4-core (RUST_LOG=solar_interface=trace):

resolve_file{path=forge-std/Base.sol}: remapped=/home/agent/dl/v4/lib/forge-std/src/Base.sol
resolve_file{path=forge-std/Base.sol}:try_file{path=/home/agent/dl/v4/lib/forge-std/src/Base.sol}: loaded from cache 1
resolve_file{path=./Base.sol}:try_file{path=lib/forge-std/src/Base.sol}: loaded from cache 2

In a trace of one OZ build, EIP712.sol is resolved 4 times via the absolute path and 10 times via the relative path. In one Seaport build, ConsiderationEnums.sol is resolved 263 times absolute and 8 times relative. The two copies then produce duplicate declarations, or a base-contract identity that doesn't match the inherited base. Every analysis error in these projects (forge build, dyn=true, Solar parser):

project errors
Uniswap/v4-core 46c6834 10 × identifier X already declared in lib/forge-std/src/Base.sol, for vm, stdstore, VM_ADDRESS, UINT256_MAX, SECP256K1_ORDER, MULTICALL3_ADDRESS, DEFAULT_TEST_CONTRACT, DEFAULT_SENDER, CREATE2_FACTORY, CONSOLE. The "previous declaration" points at the same span.
ProjectOpenSea/seaport 7f966fe already declared for ItemType ×3, OrderType ×2, BasicOrderType ×2, console, Side, ContractNonceDetails (e.g. lib/seaport-sol/src/SeaportSol.sol:5)
OpenZeppelin/openzeppelin-contracts 32b5b8c 13 × expected base class or modifier, found abstract contract for EIP712(...) in constructors of contracts that inherit EIP712 indirectly (e.g. test/account/paymaster/PaymasterSigner.t.sol:12)

solmate, solady, morpho-blue and forge-std have no analysis failures. Compiling the same projects with Solar's CLI, or forge build --use solar, reports none of these errors.

Impact

The failure makes forge give up on dynamic test linking for the whole compiler job. After that, every edit recompiles every test file: OpenZeppelin recompiles 31 files instead of 1, v4-core 60, and seaport 74. Details and numbers are in #17222, which covers the fallback behavior separately.

Repro

git clone --recursive https://github.com/Uniswap/v4-core && cd v4-core
FOUNDRY_DYNAMIC_TEST_LINKING=true RUST_LOG=foundry_common::preprocessor=warn forge build
# WARN ... dynamic test linking analysis failed; using native bytecode err=solar reported errors:
# error: identifier `VM_ADDRESS` already declared --> lib/forge-std/src/Base.sol:9:31
# note: previous declaration declared here --> lib/forge-std/src/Base.sol:9:31
echo "// edit" >> test/PoolManager.t.sol
FOUNDRY_DYNAMIC_TEST_LINKING=true forge build    # Compiling 60 files

Versions: forge nightly e342985 (1.8.4-nightly), solar 0.2.0 251aea2, solc 0.8.26 / 0.8.24 / 0.8.35.

Suggestions

Use one path form in the analysis session: pass root-relative remappings to the parser to match the relative source keys, or make every input path absolute. Alternatively, have Solar's file resolver normalize paths against its base path, so both forms map to the same source file.

Related: #17222 (fallback cost), #17204.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions