UNPKG

@fil-b/filfox-verifier

Version:

CLI tool for verifying smart contracts on Filfox explorer from hardhat and foundry projects

284 lines 11.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.FilfoxVerifier = void 0; const path = __importStar(require("path")); /** * Simplified Filfox verifier with efficient import resolution * * This implementation recursively discovers only the necessary imports, * reducing payload size and preventing verification failures due to * redundant files. */ class FilfoxVerifier { constructor(chainId) { this.baseUrl = FilfoxVerifier.FILFOX_NETWORKS[chainId]; this.chainId = chainId; if (!this.baseUrl) { throw new Error(`Unsupported chain ID: ${chainId}`); } } /** * Verifies a contract on Filfox with optimized source file resolution */ async verify(request) { try { // Parse remappings from metadata const remappings = this.parseRemappings(request.metadata.settings.remappings); // Create remapped source files const remappedSourceFiles = this.createRemappedSourceFiles(request.sourceFiles, remappings); // Resolve only necessary imports recursively const necessaryFiles = await this.resolveNecessaryImports({ sourceFiles: request.sourceFiles, remappedSourceFiles, remappings, }); // Create request body with optimized source files const requestBody = { address: request.address, language: request.language, compiler: this.normalizeCompilerVersion(request.compiler), optimize: request.optimize, optimizeRuns: request.optimizeRuns, sourceFiles: necessaryFiles, license: request.license, evmVersion: request.evmVersion, viaIR: request.viaIR, libraries: request.libraries, metadata: "", optimizerDetails: request.optimizerDetails, }; // Submit to Filfox API const response = await fetch(this.baseUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(requestBody), }); const result = await response.json(); return result; } catch (error) { console.error("Filfox verification failed:", error); return { errorCode: 8, }; } } /** * Parses remapping strings into structured format */ parseRemappings(remappingsArray) { return remappingsArray.map((mapping) => { const [original, resolved] = mapping.split("="); return { original: original.trim(), resolved: resolved.trim() }; }); } /** * Creates remapped source files based on remapping rules */ createRemappedSourceFiles(sourceFiles, remappings) { const remappedFiles = {}; for (const [filePath, fileContent] of Object.entries(sourceFiles)) { // Find matching remapping for this file path const remapping = remappings.find((mapping) => filePath.startsWith(mapping.resolved)); if (remapping) { const remappedPath = filePath.replace(remapping.resolved, remapping.original); remappedFiles[remappedPath] = fileContent; } } return remappedFiles; } /** * Recursively resolves only the necessary imports for compilation * This is the core optimization that reduces redundant files */ async resolveNecessaryImports(resolver) { const necessaryFiles = {}; const processed = new Set(); const allAvailableFiles = { ...resolver.sourceFiles, ...resolver.remappedSourceFiles, }; // Start with entry source files, prioritizing remapped versions const queue = []; // Add remapped versions first (they take precedence) for (const remappedPath of Object.keys(resolver.remappedSourceFiles)) { queue.push(remappedPath); } // Add non-remapped files that don't have remapped equivalents for (const originalPath of Object.keys(resolver.sourceFiles)) { const hasRemappedVersion = Object.keys(resolver.remappedSourceFiles).some((remappedPath) => { // Check if this original file has a corresponding remapped version return resolver.remappings.some((mapping) => originalPath.startsWith(mapping.resolved) && remappedPath === originalPath.replace(mapping.resolved, mapping.original)); }); if (!hasRemappedVersion) { queue.push(originalPath); } } while (queue.length > 0) { const currentFile = queue.shift(); if (processed.has(currentFile)) { continue; } processed.add(currentFile); // Add current file to necessary files if available if (allAvailableFiles[currentFile]) { necessaryFiles[currentFile] = allAvailableFiles[currentFile]; // Extract imports from this file const imports = this.extractImports(allAvailableFiles[currentFile].content); // Resolve each import and add to queue for (const importPath of imports) { const resolvedImport = this.resolveImportPath(importPath, currentFile, resolver.remappings, allAvailableFiles); if (resolvedImport && !processed.has(resolvedImport.path)) { queue.push(resolvedImport.path); } } } } return necessaryFiles; } /** * Extracts import statements from Solidity source code */ extractImports(content) { const imports = []; const importRegexes = [ /import\s+["']([^"']+)["']/g, // import "path" /import\s*\{[^}]*\}\s*from\s+["']([^"']+)["']/g, // import {Symbol} from "path" /import\s+\*\s+as\s+\w+\s+from\s+["']([^"']+)["']/g, // import * as Symbol from "path" /import\s+\w+\s+from\s+["']([^"']+)["']/g, // import Symbol from "path" ]; for (const regex of importRegexes) { let match; while ((match = regex.exec(content)) !== null) { const importPath = match[1]; if (importPath && !imports.includes(importPath)) { imports.push(importPath); } } } return imports; } /** * Resolves an import path to the actual file path using compiler logic */ resolveImportPath(importPath, currentFile, remappings, availableFiles) { // Strategy 1: Check if import path exists directly if (availableFiles[importPath]) { return { path: importPath, content: availableFiles[importPath] }; } // Strategy 2: Handle relative imports (./ ../) if (importPath.startsWith("./") || importPath.startsWith("../")) { const resolvedPath = this.resolveRelativeImport(importPath, currentFile); if (availableFiles[resolvedPath]) { return { path: resolvedPath, content: availableFiles[resolvedPath] }; } } // Strategy 3: Handle remapped imports const remappedPath = this.resolveRemappedImport(importPath, remappings); if (remappedPath && availableFiles[remappedPath]) { return { path: remappedPath, content: availableFiles[remappedPath] }; } // Strategy 4: Search in available files by filename const fileName = path.basename(importPath); for (const [filePath, fileContent] of Object.entries(availableFiles)) { if (path.basename(filePath) === fileName) { // Additional check: ensure path context makes sense if (this.pathContextMatches(importPath, filePath)) { return { path: filePath, content: fileContent }; } } } console.warn(`⚠️ Could not resolve import: ${importPath} from ${currentFile}`); return null; } /** * Resolves relative imports based on current file context */ resolveRelativeImport(importPath, currentFile) { const currentDir = path.dirname(currentFile); return path.normalize(path.join(currentDir, importPath)); } /** * Resolves imports using remapping rules */ resolveRemappedImport(importPath, remappings) { // Find best matching remapping (longest match first) const sortedRemappings = remappings .filter((mapping) => importPath.startsWith(mapping.original)) .sort((a, b) => b.original.length - a.original.length); if (sortedRemappings.length > 0) { const mapping = sortedRemappings[0]; return importPath.replace(mapping.original, mapping.resolved); } return null; } /** * Checks if import path context matches the available file path */ pathContextMatches(importPath, availablePath) { const importSegments = importPath .split("/") .filter((s) => s && s !== "." && s !== ".."); const availableSegments = availablePath .split("/") .filter((s) => s && s !== "." && s !== ".."); // Count matching segments let matches = 0; for (const segment of importSegments) { if (availableSegments.includes(segment)) { matches++; } } // If most segments match, consider it a valid match return matches >= Math.max(1, importSegments.length - 1); } /** * Ensures compiler version has 'v' prefix */ normalizeCompilerVersion(compiler) { return compiler.includes("v") ? compiler : `v${compiler}`; } } exports.FilfoxVerifier = FilfoxVerifier; FilfoxVerifier.FILFOX_NETWORKS = { 314: "https://filfox.info/api/v1/tools/verifyContract", 314159: "https://calibration.filfox.info/api/v1/tools/verifyContract", }; //# sourceMappingURL=filfox-verifier.js.map