@fil-b/filfox-verifier
Version:
CLI tool for verifying smart contracts on Filfox explorer from hardhat and foundry projects
284 lines • 11.8 kB
JavaScript
;
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