From a9a162fd31cfb14c7112e479b89ea2438a49e426 Mon Sep 17 00:00:00 2001 From: Armando Andini Date: Wed, 7 Dec 2022 09:46:31 -0300 Subject: [PATCH 1/2] fix: foundry root imports --- .../src/frameworks/Foundry/FoundryProject.ts | 46 +++++++++++++------ .../services/validation/CompilationService.ts | 14 ++++++ server/src/utils/directoryContains.ts | 13 ++++-- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/server/src/frameworks/Foundry/FoundryProject.ts b/server/src/frameworks/Foundry/FoundryProject.ts index e2cdceb8..bcecbe62 100644 --- a/server/src/frameworks/Foundry/FoundryProject.ts +++ b/server/src/frameworks/Foundry/FoundryProject.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import fs from "fs"; +import { existsSync } from "fs"; import _ from "lodash"; import path from "path"; import { @@ -98,24 +98,32 @@ export class FoundryProject extends Project { } public async resolveImportPath(file: string, importPath: string) { - try { - let transformedPath = importPath; + let transformedPath = importPath; - if (!importPath.startsWith(".")) { - for (const { from, to } of this.remappings) { - if (importPath.startsWith(from)) { - transformedPath = path.join(to, importPath.slice(from.length)); - } + if (!importPath.startsWith(".")) { + for (const { from, to } of this.remappings) { + if (importPath.startsWith(from)) { + transformedPath = path.join(to, importPath.slice(from.length)); } } - const resolvedPath = require.resolve(transformedPath, { - paths: [fs.realpathSync(path.dirname(file))], - }); + } + + // Try to resolve the import recursively, start from source directory up to project root + let testBaseDirectory = path.dirname(file); + let resolvedPath: string | undefined; + + while (directoryContains(this.basePath, testBaseDirectory)) { + const testResolvedPath = path.resolve(testBaseDirectory, transformedPath); + + if (existsSync(testResolvedPath)) { + resolvedPath = testResolvedPath; + break; + } - return toUnixStyle(fs.realpathSync(resolvedPath)); - } catch (error) { - return undefined; + testBaseDirectory = path.dirname(testBaseDirectory); } + + return resolvedPath !== undefined ? toUnixStyle(resolvedPath) : undefined; } public async buildCompilation( @@ -133,6 +141,16 @@ export class FoundryProject extends Project { this.configSolcVersion ); + const sources = basicCompilation.input.sources; + + // Modify source keys to be root-relative instead of absolute + // i,e, '/home/user/myProject/src/Contract.sol' => 'src/Contract.sol' + for (const [sourceKey, sourceValue] of Object.entries(sources)) { + const transformedSourceKey = path.relative(this.basePath, sourceKey); + sources[transformedSourceKey] = sourceValue; + delete sources[sourceKey]; + } + const remappings = this.remappings.map( (remapping) => `${remapping.from}=${remapping.to}` ); diff --git a/server/src/services/validation/CompilationService.ts b/server/src/services/validation/CompilationService.ts index 8a61400e..60e1bee9 100644 --- a/server/src/services/validation/CompilationService.ts +++ b/server/src/services/validation/CompilationService.ts @@ -4,15 +4,18 @@ import { existsSync } from "fs"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import path from "path"; import { CompilationDetails } from "../../frameworks/base/CompilationDetails"; +import { Logger } from "../../utils/Logger"; export class CompilationService { public static async compile( { cachedCompilerInfo, + logger, }: { cachedCompilerInfo: { [solcVersion: string]: { isSolcJs: boolean; compilerPath: string }; }; + logger: Logger; }, compilationDetails: CompilationDetails ): Promise { @@ -22,6 +25,17 @@ export class CompilationService { // Empty outputSelection for faster compilation delete (input.settings as any).outputSelection; + logger.trace( + `Solc Input: ${JSON.stringify( + { + ...compilationDetails.input, + sources: Object.keys(compilationDetails.input.sources), + }, + null, + 2 + )}` + ); + // Find or download solc compiler let compilerPath: string; let isSolcJs: boolean; diff --git a/server/src/utils/directoryContains.ts b/server/src/utils/directoryContains.ts index 1c8ef3ff..3f212dbc 100644 --- a/server/src/utils/directoryContains.ts +++ b/server/src/utils/directoryContains.ts @@ -1,10 +1,13 @@ import path from "path"; +/** + * Returns true if testPath is equal to dirPath or a subdirectory of it + * @param dirPath + * @param testPath + * @returns + */ export function directoryContains(dirPath: string, testPath: string): boolean { const relative = path.relative(dirPath, testPath); - return ( - !!relative && - !relative.startsWith(`..${path.sep}`) && - !path.isAbsolute(relative) - ); + + return !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative); } From 859f39738b048bee31f40a8683f101af50fdcd5d Mon Sep 17 00:00:00 2001 From: Armando Andini Date: Mon, 12 Dec 2022 15:29:11 -0300 Subject: [PATCH 2/2] add tests for foundry's resolveImport --- .../src/frameworks/Foundry/FoundryProject.ts | 1 + .../frameworks/foundry/FoundryProject.test.ts | 90 +++++++++++++++++++ server/test/frameworks/foundry/Illegal.sol | 0 .../frameworks/foundry/test_project/lib/C.sol | 0 .../frameworks/foundry/test_project/src/A.sol | 0 .../frameworks/foundry/test_project/src/B.sol | 0 .../foundry/test_project/src/nested/D.sol | 0 .../foundry/test_project/src/nested/E.sol | 0 8 files changed, 91 insertions(+) create mode 100644 server/test/frameworks/foundry/FoundryProject.test.ts create mode 100644 server/test/frameworks/foundry/Illegal.sol create mode 100644 server/test/frameworks/foundry/test_project/lib/C.sol create mode 100644 server/test/frameworks/foundry/test_project/src/A.sol create mode 100644 server/test/frameworks/foundry/test_project/src/B.sol create mode 100644 server/test/frameworks/foundry/test_project/src/nested/D.sol create mode 100644 server/test/frameworks/foundry/test_project/src/nested/E.sol diff --git a/server/src/frameworks/Foundry/FoundryProject.ts b/server/src/frameworks/Foundry/FoundryProject.ts index bcecbe62..3ae038f0 100644 --- a/server/src/frameworks/Foundry/FoundryProject.ts +++ b/server/src/frameworks/Foundry/FoundryProject.ts @@ -100,6 +100,7 @@ export class FoundryProject extends Project { public async resolveImportPath(file: string, importPath: string) { let transformedPath = importPath; + // Apply remappings to importPath if it's not a relative import if (!importPath.startsWith(".")) { for (const { from, to } of this.remappings) { if (importPath.startsWith(from)) { diff --git a/server/test/frameworks/foundry/FoundryProject.test.ts b/server/test/frameworks/foundry/FoundryProject.test.ts new file mode 100644 index 00000000..ee72dfdc --- /dev/null +++ b/server/test/frameworks/foundry/FoundryProject.test.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { expect } from "chai"; +import path from "path"; +import { stub } from "sinon"; +import * as basicCompilation from "../../../src/frameworks/shared/buildBasicCompilation"; +import { FoundryProject } from "../../../src/frameworks/Foundry/FoundryProject"; +import { ServerState } from "../../../src/types"; +import { toUnixStyle } from "../../../src/utils"; + +describe("FoundryProject", function () { + let project: FoundryProject; + const serverStateMock = { + logger: {}, + } as ServerState; + + beforeEach(async () => { + project = new FoundryProject( + serverStateMock, + path.join(__dirname, "test_project"), + path.join(__dirname, "test_project", "foundry.toml") + ); + }); + + describe("resolveImportPath", function () { + it("resolves relative imports", async () => { + const foundImport = await project.resolveImportPath( + path.join(project.basePath, "src", "A.sol"), + "./B.sol" + ); + const notFoundImport = await project.resolveImportPath( + path.join(project.basePath, "src", "A.sol"), + "./C.sol" + ); + expect(foundImport).to.eq( + toUnixStyle(path.join(project.basePath, "src", "B.sol")) + ); + expect(notFoundImport).to.eq(undefined); + }); + + it("resolves root imports", async () => { + const importFromSameLevel = await project.resolveImportPath( + path.join(project.basePath, "src", "nested", "D.sol"), + "nested/E.sol" + ); + const importFromParent = await project.resolveImportPath( + path.join(project.basePath, "src", "nested", "D.sol"), + "src/A.sol" + ); + const importFromLib = await project.resolveImportPath( + path.join(project.basePath, "src", "nested", "D.sol"), + "lib/C.sol" + ); + const illegalImport = await project.resolveImportPath( + path.join(project.basePath, "src", "A.sol"), + "foundry/Illegal.sol" + ); + + expect(importFromSameLevel).to.eq( + toUnixStyle(path.join(project.basePath, "src", "nested", "E.sol")) + ); + expect(importFromParent).to.eq( + toUnixStyle(path.join(project.basePath, "src", "A.sol")) + ); + expect(importFromLib).to.eq( + toUnixStyle(path.join(project.basePath, "lib", "C.sol")) + ); + expect(illegalImport).to.eq(undefined); + }); + }); + + describe("buildCompilation", function () { + it("replaces absolute paths provided by buildBasicCompilation with root-relative paths", async () => { + const sourceUri = path.join(project.basePath, "src", "A.sol"); + + stub(basicCompilation, "buildBasicCompilation").resolves({ + input: { + sources: { + [sourceUri]: { content: "" }, + }, + settings: {}, + }, + } as any); + + const compilation = await project.buildCompilation(sourceUri, []); + expect(compilation.input.sources).to.deep.eq({ + [path.join("src", "A.sol")]: { content: "" }, + }); + }); + }); +}); diff --git a/server/test/frameworks/foundry/Illegal.sol b/server/test/frameworks/foundry/Illegal.sol new file mode 100644 index 00000000..e69de29b diff --git a/server/test/frameworks/foundry/test_project/lib/C.sol b/server/test/frameworks/foundry/test_project/lib/C.sol new file mode 100644 index 00000000..e69de29b diff --git a/server/test/frameworks/foundry/test_project/src/A.sol b/server/test/frameworks/foundry/test_project/src/A.sol new file mode 100644 index 00000000..e69de29b diff --git a/server/test/frameworks/foundry/test_project/src/B.sol b/server/test/frameworks/foundry/test_project/src/B.sol new file mode 100644 index 00000000..e69de29b diff --git a/server/test/frameworks/foundry/test_project/src/nested/D.sol b/server/test/frameworks/foundry/test_project/src/nested/D.sol new file mode 100644 index 00000000..e69de29b diff --git a/server/test/frameworks/foundry/test_project/src/nested/E.sol b/server/test/frameworks/foundry/test_project/src/nested/E.sol new file mode 100644 index 00000000..e69de29b