//Simple parser for .sol files that finds libraries defined in the file and creates a wrapper // that converts all their internal functions to external functions so they can be tested // in forge unit tests. //Instead of properly parsing the full AST, we make our task of finding and transforming the // relevant bits easier by making some basic assumptions about code formatting that any sane // code should almost certainly adhere to regardless. //Solidity language grammar: // https://docs.soliditylang.org/en/latest/grammar.html import { readFileSync } from "fs"; // const commentTestCode = ` // // /* commented-out block comment start // code // // commented-out block comment end */ // const x = 42; // inline comment // /* block comment // */ // code after comment // // commented-out block comment end */ // `; function removeComments(code: string): string { //?"); process.exit(1); } const filename = process.argv[2]; let fileCode = readFileSync("../src/" + filename + filename.endsWith(".sol") ? "" : ".sol", "utf8"); //we first remove all comments so we can be sure that everything we're parsing is actual code fileCode = removeComments(fileCode); interface Func { name: string; stateMut: string; paras: string[]; rets?: string[]; } const libraries: Record = {}; const libRegex = /library\s+(\w+)\s*\{([\s\S]*?)\n\}/g; //use library regex to find all libraries in the file and split into name and code pairs const libMatches = fileCode.matchAll(libRegex); for (const libs of libMatches) { const [_, name, code] = libs; libraries[name] = []; const structRegex = /\s*struct\s+(\w+)/g; const structs = new Set(); const structMatches = code.matchAll(structRegex); for (const struct of structMatches) structs.add[struct[1]]; const funcRegex = /\s*function\s+(\w+)\s*\(([\s\S]*?)\)([\s\S]*?)([\{;])/g; const funcMatches = code.matchAll(funcRegex); for (const funcs of funcMatches) { const [_, funcName, funcParasRaw, modsRaw, close] = funcs; if (close == ';') continue; //function pointer, not a function definition if (!modsRaw.includes("internal")) continue; //not an internal function const retParasRegex = /returns\s*\(([\s\S]*?)\)/; const retParasRaw = modsRaw.match(retParasRegex); const collapseSpaceRegex = /\s\s+/g; const paramsToArray = (paramList: string) => paramList.replace(collapseSpaceRegex, ' ').trim().split(',').map(param => { param = param.trim(); const paraType = param.match(/^(\w+)/)[1]; return structs.has(paraType) ? param.replace(paraType, `${name}.${paraType}`) : param; }); libraries[name].push({ name: funcName, stateMut: modsRaw.match(/\b(pure|view)\b/)?.[0] ?? '', paras: paramsToArray(funcParasRaw.replace(/\bmemory\b/g, ' calldata ')), rets: retParasRaw ? paramsToArray(retParasRaw[1]) : undefined, }); } } console.log(`// SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.24; //This file was auto-generated by libraryTestWrapper.ts import "wormhole-sdk/${filename}.sol";`); const pConcat = (paras: string[]) => (paras.length > 2 ? "\n " : "") + paras.join(paras.length > 2 ? ",\n " : ", ") + (paras.length > 2 ? "\n " : ""); const pNames = (paras: string[]) => paras.map(para => { const pieces = para.split(" "); return pieces[pieces.length-1]; }); for (const [libName, funcs] of Object.entries(libraries)) { console.log(`\ncontract ${libName}TestWrapper {`); const funcCode = []; for (const func of funcs) funcCode.push([ ` function ${func.name}(${pConcat(func.paras)}) external ${func.stateMut}` + ` ${func.rets ? `returns (${pConcat(func.rets)}) ` : ''} {`, ` ${func.rets ? 'return ' : ''}${libName}.${func.name}(${pNames(func.paras).join(', ')});`, ` }` ].join('\n')); console.log(funcCode.join('\n\n')); console.log('}'); }