From 23587ad1eae7fc2c5cd00cb2c80693f423836e54 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:08:11 -0300 Subject: [PATCH] fix: refactor remappings and add test import (#72) --- src/smock-foundry.ts | 11 ++++-- src/templates/contract-template.hbs | 2 +- src/templates/helper-template.hbs | 2 +- src/utils.ts | 53 ++++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/smock-foundry.ts b/src/smock-foundry.ts index 8f042d8..2191b5f 100644 --- a/src/smock-foundry.ts +++ b/src/smock-foundry.ts @@ -8,6 +8,8 @@ import { smockableNode, compileSolidityFilesFoundry, renderAbstractUnimplementedFunctions, + getRemappings, + getTestImport, } from './utils'; import path from 'path'; import { ensureDir } from 'fs-extra'; @@ -31,7 +33,9 @@ export async function generateMockContracts( console.log('Parsing contracts...'); try { - const sourceUnits = await getSourceUnits(rootPath, contractsDirectories, ignoreDirectories); + const remappings: string[] = await getRemappings(rootPath); + const testImport = getTestImport(remappings); + const sourceUnits = await getSourceUnits(rootPath, contractsDirectories, ignoreDirectories, remappings); if (!sourceUnits.length) return console.error('No solidity files found in the specified directory'); @@ -80,6 +84,7 @@ export async function generateMockContracts( sourceContractRelativePath: sourceContractRelativePath, exportedSymbols: Array.from(scope.exportedSymbols.keys()), license: sourceUnit.license, + testImport, }); await ensureDir(path.dirname(mockContractAbsolutePath)); @@ -89,12 +94,12 @@ export async function generateMockContracts( // Generate SmockHelper contract const smockHelperTemplate = await getSmockHelperTemplate(); - const smockHelperCode: string = smockHelperTemplate({}); + const smockHelperCode: string = smockHelperTemplate({ testImport }); writeFileSync(`${mocksPath}/SmockHelper.sol`, smockHelperCode); console.log('Mock contracts generated successfully'); - await compileSolidityFilesFoundry(rootPath, mocksDirectory); + await compileSolidityFilesFoundry(rootPath, mocksDirectory, remappings); // Format the generated files console.log('Formatting generated files...'); diff --git a/src/templates/contract-template.hbs b/src/templates/contract-template.hbs index fe646cf..99c0a99 100644 --- a/src/templates/contract-template.hbs +++ b/src/templates/contract-template.hbs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: {{#if license}}{{license}}{{else}}UNLICENSED{{/if}} pragma solidity ^0.8.0; -import { Test } from 'forge-std/Test.sol'; +import { Test } from '{{testImport}}'; import { {{~exportedSymbols~}} } from '{{sourceContractRelativePath}}'; {{importsContent}} diff --git a/src/templates/helper-template.hbs b/src/templates/helper-template.hbs index 442961e..37a5e2c 100644 --- a/src/templates/helper-template.hbs +++ b/src/templates/helper-template.hbs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {Test} from 'forge-std/Test.sol'; +import {Test} from '{{testImport}}'; contract SmockHelper is Test { function deployMock( diff --git a/src/utils.ts b/src/utils.ts index 7685fbf..1081115 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -31,6 +31,7 @@ import { externalOrPublicFunctionContext, internalFunctionContext, } from './context'; +import { exec } from 'child_process'; /** * Fixes user-defined types @@ -98,13 +99,11 @@ export function getSmockHelperTemplate(): HandlebarsTemplateDelegate { * Compiles the solidity files in the given directory calling forge build command * @param mockContractsDir The directory of the generated contracts */ -export async function compileSolidityFilesFoundry(rootPath: string, mockContractsDir: string) { +export async function compileSolidityFilesFoundry(rootPath: string, mockContractsDir: string, remappings: string[]) { console.log('Compiling contracts...'); try { const solidityFiles: string[] = await getSolidityFilesAbsolutePaths(rootPath, [mockContractsDir]); - const remappings: string[] = await getRemappings(rootPath); - await compileSol(solidityFiles, 'auto', { basePath: rootPath, remapping: remappings, @@ -245,7 +244,12 @@ export async function getRemappings(rootPath: string): Promise { try { return await exports.getRemappingsFromConfig(path.join(rootPath, 'foundry.toml')); } catch { - return []; + // If neither file exists, try to generate the remappings using forge + try { + return await exports.getRemappingsFromForge(); + } catch { + return []; + } } } } @@ -272,10 +276,25 @@ export async function getRemappingsFromConfig(foundryConfigPath: string): Promis .filter((line) => line.length) .map((line) => sanitizeRemapping(line)); } else { - return []; + throw new Error('No remappings found in foundry.toml'); } } +/** + * Returns the remappings generated by forge + * @returns {Promise} - The list of remappings + */ +export async function getRemappingsFromForge(): Promise { + const remappingsContent = await new Promise((resolve, reject) => + exec('forge remappings', { encoding: 'utf8' }, (error, stdout) => (error ? reject(error) : resolve(stdout))), + ); + return remappingsContent + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length) + .map((line) => sanitizeRemapping(line)); +} + export function sanitizeRemapping(line: string): string { // Make sure the key and the value both either have or don't have a trailing slash const [key, value] = line.split('='); @@ -304,12 +323,30 @@ export async function emptySmockDirectory(mocksDirectory: string) { } } -export async function getSourceUnits(rootPath: string, contractsDirectories: string[], ignoreDirectories: string[]): Promise { +export function getTestImport(remappings: string[]): string { + const module = 'forge-std'; + + for (const remapping of remappings) { + const [alias, path] = remapping.split('='); // Split remapping into alias and path + + if (alias.startsWith(module) && path.includes(module)) { + const srcPath = path.includes('/src/') ? '' : `src/`; + return `${alias}${srcPath}Test.sol`; + } + } + + return 'forge-std/src/Test.sol'; +} + +export async function getSourceUnits( + rootPath: string, + contractsDirectories: string[], + ignoreDirectories: string[], + remappings: string[], +): Promise { const files: string[] = await getSolidityFilesAbsolutePaths(rootPath, contractsDirectories); const solidityFiles = files.filter((file) => !ignoreDirectories.some((directory) => file.includes(directory))); - const remappings: string[] = await getRemappings(rootPath); - const compiledFiles = await compileSol(solidityFiles, 'auto', { basePath: rootPath, remapping: remappings,