fix(plugin): resolve contract sources from nested directories - #11
Merged
MatiasOS merged 3 commits intoSep 10, 2026
Merged
Conversation
Source lookup took the basename of sourceName and rejoined it to a hardcoded contracts/, so a contract at contracts/core/Counter.sol was looked for at contracts/Counter.sol. Every contract in a project with subdirectories got no source, and a contract compiled from outside contracts/ could never resolve at all. existsSync failed silently, so pages rendered with a working ABI and an empty source view. sourceName is already project-relative, so join it as-is. When the file is not on disk - moved or deleted since compilation, or an npm dependency rather than a project file - recover the text from the solc standard JSON input already shipped in the build info, keyed by the artifact's inputSourceName or the build info's userSourceNameMap. Both call sites shared the construct, so the resolution lives in one place. A miss now warns once per source name: the artifact loader re-reads the deployment on every page request, so an ungated warning would repeat on every refresh.
The example project kept a single flat contracts/Counter.sol - the one layout where the old basename-based source lookup happened to be correct, which is why the bug went unnoticed. Move Counter into contracts/core/ and add a TestToken in contracts/mocks/, deployed from the Ignition module, so both the Ignition and raw-deploy paths are exercised against more than one subdirectory. Narrow the ignore from /ignition to /ignition/deployments. Only the deployment state is regenerated per run; the module is source, and the README's documented deploy step references it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes openscan-explorer/explorer#399
Problem
Source lookup took the basename of
sourceNameand rejoined it to a hardcoded<projectRoot>/contracts/. SincesourceNameis already project-relative, a contract atcontracts/core/Counter.solwas looked for atcontracts/Counter.sol.Every contract in a project with subdirectories got no source, and a contract compiled from outside
contracts/could never resolve at all.existsSyncfailed silently, so contract pages rendered with a working ABI and build info but an empty source view — reading as a partial failure rather than a path bug.Both code paths shared the construct:
artifacts.ts(Ignition deployments) anddeployment-tracker.ts(raweth_sendTransactiondeploys).Fix
sourceNameis already project-relative, so it is now joined as-is — correct for flat, nested, and outside-contracts/layouts alike.When the file is not on disk, the source text is recovered from the solc standard JSON input that already ships in the build info alongside every artifact. Hardhat 3 artifacts carry an
inputSourceNamethat is exactly the key intoinput.sources, withuserSourceNameMapin the build info as a second route, so this is an exact lookup rather than a heuristic:That covers the one case the path fix alone cannot: a
sourceNamethat is not a project-relative path at all, such as a directly deployed npm dependency (npm/@openzeppelin/contracts@5.x/proxy/ERC1967/ERC1967Proxy.sol).Resolution now lives in one place,
src/source-resolver.ts, used by both call sites. A miss warns once per source name — the artifact loader re-reads the deployment on every page request, so an ungated warning would repeat on every refresh.deployment-tracker.tsloaded build info after source; those blocks are swapped, since the fallback depends on it.Example project
packages/example-projectkept a single flatcontracts/Counter.sol— the one layout where the old lookup happened to be correct, which is why this went unnoticed.Countermoves tocontracts/core/and aTestTokenis added incontracts/mocks/, deployed together from the Ignition module, so both deploy paths are exercised against more than one subdirectory.The ignore is narrowed from
/ignitionto/ignition/deployments. Only the deployment state is regenerated per run;ignition/modules/Counter.tsis source, and the README's documented deploy step references it, so it should be in the repo.Verification
Ran the node and explorer and inspected the artifact payload the plugin injects into the page, rather than eyeballing the UI:
0x5fbd…Counter(contracts/core/)0xe7f1…TestToken(contracts/mocks/)0xcf7e…Counter(raw deploy,DeploymentTracker)Also confirmed against the same fixture:
contracts/, and npm-stylesourceNameall resolve; unknown keys, absent, malformed andnullbuild info all returnundefinedwithout throwingpnpm buildandpnpm lintpass;Counter.t.sol's 3 tests still pass after the moveNot included
projectRootisprocess.cwd()in both entry points (server.ts,hooks/network.ts) rather thanhre.config.paths.root, andpaths.sourcesis ignored, so a project that configures a non-default sources directory or is driven from another working directory still resolves against cwd. This fix is correct whenever cwd is the project root, which is the normal case; making it independent of cwd is a separate change and I'm happy to open a follow-up issue.No tests are added here — the plugin currently has no
testscript at all, so rootpnpm testis a no-op and CI's Test step does nothing. Restoring that harness felt like it belonged in its own PR; say the word and I'll add it with the nested fixtures checked in as JSON.