@fil-b/filfox-verifier
Version:
CLI tool for verifying smart contracts on Filfox explorer from hardhat and foundry projects
248 lines • 11.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContractDataExtractor = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
class ContractDataExtractor {
static async extractContractData(network, address, deploymentsPath, hre) {
try {
return this.extractFromHardhatDeployments(network, address, deploymentsPath, hre);
}
catch (error) {
// Hardhat deployments not found, trying using Ignition configuration fallback...
try {
return this.extractFromIgnitionDeployments(address, hre);
}
catch (ignitionError) {
// Ignition deployments not found, trying using artifacts fallback...
return await this.extractFromArtifacts(address, hre);
}
}
}
static extractFromHardhatDeployments(network, address, deploymentsPath, hre) {
// Search for the contract name in the deployments/network directory for the address
const deploymentFiles = fs_1.default
.readdirSync(`${deploymentsPath}/${network}`)
.filter((file) => file.endsWith(".json"));
const contractFileName = deploymentFiles.find((fileName) => {
try {
const filePath = `${deploymentsPath}/${network}/${fileName}`;
const deployment = JSON.parse(fs_1.default.readFileSync(filePath, "utf8"));
return deployment.address?.toLowerCase() === address?.toLowerCase();
}
catch (error) {
console.warn(`Warning: Could not read deployment file ${fileName}:`, error);
return false;
}
});
if (!contractFileName) {
throw new Error(`No deployment file found for contract address ${address} in ${deploymentsPath}/${network}`);
}
// Extract contract name by removing .json extension
const contractName = contractFileName.replace(".json", "");
const deploymentPath = `${deploymentsPath}/${network}/${contractFileName}`;
const deployments = JSON.parse(fs_1.default.readFileSync(deploymentPath, "utf8"));
const solcInputPath = `${deploymentsPath}/${network}/solcInputs/${deployments.solcInputHash}.json`;
const solcInput = JSON.parse(fs_1.default.readFileSync(solcInputPath, "utf8"));
let sourceFiles = Object.keys(solcInput.sources).reduce((acc, key) => {
acc[key] = solcInput.sources[key];
return acc;
}, {});
const contractToVerify = Object.keys(sourceFiles).find((key) => key.includes(contractName + ".sol"));
if (!contractToVerify) {
throw new Error(`Contract ${contractName} not found in the sources provided.`);
}
const contractSource = sourceFiles[contractToVerify];
delete sourceFiles[contractToVerify];
sourceFiles = { [contractToVerify]: contractSource, ...sourceFiles };
const { compiler, language } = JSON.parse(deployments.metadata);
const compilerVersion = "v" + compiler.version;
const optimize = solcInput.settings.optimizer.enabled;
const optimizeRuns = solcInput.settings.optimizer.runs;
const license = "";
const evmVersion = solcInput.settings.evmVersion ?? "default";
const viaIR = hre
? !!hre.userConfig.solidity?.settings?.viaIR
: false;
const libraries = "";
const metadata = solcInput.settings.metadata ?? "";
return {
address: deployments.address,
language,
compiler: compilerVersion,
optimize,
optimizeRuns,
sourceFiles,
license,
evmVersion,
viaIR,
libraries,
metadata,
optimizerDetails: "",
};
}
static extractFromIgnitionDeployments(address, hre) {
const chainId = hre?.network.config.chainId;
if (!chainId) {
throw new Error("Chain ID not found for Ignition deployment extraction");
}
const ignitionPath = "./ignition/deployments";
const chainFolderName = `chain-${chainId}`;
const deployedAddressesPath = `${ignitionPath}/${chainFolderName}/deployed_addresses.json`;
if (!fs_1.default.existsSync(deployedAddressesPath)) {
throw new Error(`No Ignition deployments found at ${deployedAddressesPath}`);
}
const deployedAddresses = JSON.parse(fs_1.default.readFileSync(deployedAddressesPath, "utf8"));
const deploymentKey = Object.keys(deployedAddresses).find((key) => deployedAddresses[key].toLowerCase() === address.toLowerCase());
if (!deploymentKey) {
throw new Error(`No deployment found for address ${address} in Ignition deployments`);
}
const contractName = deploymentKey.split("#")[1];
if (!contractName) {
throw new Error(`Invalid deployment key format: ${deploymentKey}. Expected format: Module#ContractName`);
}
const artifactsPath = `${ignitionPath}/${chainFolderName}/artifacts`;
const dbgFileName = `${deploymentKey}.dbg.json`;
const dbgFilePath = `${artifactsPath}/${dbgFileName}`;
if (!fs_1.default.existsSync(dbgFilePath)) {
throw new Error(`Debug file not found at ${dbgFilePath}`);
}
const dbgData = JSON.parse(fs_1.default.readFileSync(dbgFilePath, "utf8"));
const buildInfoPath = `${artifactsPath}/${dbgData.buildInfo}`;
if (!fs_1.default.existsSync(buildInfoPath)) {
throw new Error(`Build info file not found at ${buildInfoPath}`);
}
const buildInfo = JSON.parse(fs_1.default.readFileSync(buildInfoPath, "utf8"));
let sourceFiles = Object.keys(buildInfo.input.sources).reduce((acc, key) => {
acc[key] = buildInfo.input.sources[key];
return acc;
}, {});
const contractToVerify = Object.keys(sourceFiles).find((key) => key.includes(contractName + ".sol"));
if (!contractToVerify) {
throw new Error(`Contract ${contractName} not found in the build info sources.`);
}
const contractSource = sourceFiles[contractToVerify];
delete sourceFiles[contractToVerify];
sourceFiles = { [contractToVerify]: contractSource, ...sourceFiles };
const compilerVersion = "v" + buildInfo.solcLongVersion;
const optimize = buildInfo.input.settings.optimizer.enabled;
const optimizeRuns = buildInfo.input.settings.optimizer.runs;
const license = "";
const evmVersion = buildInfo.input.settings.evmVersion ?? "default";
const viaIR = buildInfo.input.settings.viaIR ?? false;
const libraries = "";
const metadata = buildInfo.input.settings.metadata ?? "";
return {
address: address,
language: buildInfo.input.language,
compiler: compilerVersion,
optimize,
optimizeRuns,
sourceFiles,
license,
evmVersion,
viaIR,
libraries,
metadata,
optimizerDetails: "",
};
}
static async extractFromArtifacts(address, hre) {
if (!hre) {
throw new Error("HardhatRuntimeEnvironment is required for artifacts extraction");
}
const bytecode = (await hre.network.provider.request({
method: "eth_getCode",
params: [address, "latest"],
}));
if (!bytecode || bytecode === "0x") {
throw new Error(`Bytecode not found for address ${address}`);
}
const artifactsPath = "./artifacts/contracts";
if (!fs_1.default.existsSync(artifactsPath)) {
throw new Error(`Artifacts directory not found at ${artifactsPath}`);
}
const matchingContract = this.findMatchingContractInArtifacts(artifactsPath, bytecode);
if (!matchingContract) {
throw new Error(`No matching contract found for address ${address} in artifacts`);
}
const dbgFile = matchingContract.artifactPath.replace(".json", ".dbg.json");
if (!fs_1.default.existsSync(dbgFile)) {
throw new Error(`Debug file not found at ${dbgFile}`);
}
const dbgData = JSON.parse(fs_1.default.readFileSync(dbgFile, "utf8"));
const buildInfoPath = path_1.default.resolve(path_1.default.dirname(dbgFile), dbgData.buildInfo);
if (!fs_1.default.existsSync(buildInfoPath)) {
throw new Error(`Build info file not found at ${buildInfoPath}`);
}
const buildInfo = JSON.parse(fs_1.default.readFileSync(buildInfoPath, "utf8"));
let sourceFiles = Object.keys(buildInfo.input.sources).reduce((acc, key) => {
acc[key] = buildInfo.input.sources[key];
return acc;
}, {});
const contractToVerify = Object.keys(sourceFiles).find((key) => key.includes(matchingContract.contractName + ".sol"));
if (!contractToVerify) {
throw new Error(`Contract ${matchingContract.contractName} not found in the build info sources.`);
}
const contractSource = sourceFiles[contractToVerify];
delete sourceFiles[contractToVerify];
sourceFiles = { [contractToVerify]: contractSource, ...sourceFiles };
const compilerVersion = "v" + buildInfo.solcLongVersion;
const optimize = buildInfo.input.settings.optimizer.enabled;
const optimizeRuns = buildInfo.input.settings.optimizer.runs;
const license = "";
const evmVersion = buildInfo.input.settings.evmVersion ?? "default";
const viaIR = buildInfo.input.settings.viaIR ?? false;
const libraries = "";
const metadata = buildInfo.input.settings.metadata ?? "";
return {
address: address,
language: buildInfo.input.language,
compiler: compilerVersion,
optimize,
optimizeRuns,
sourceFiles,
license,
evmVersion,
viaIR,
libraries,
metadata,
optimizerDetails: "",
};
}
static findMatchingContractInArtifacts(artifactsPath, targetBytecode) {
const searchDirectory = (dir) => {
const items = fs_1.default.readdirSync(dir);
for (const item of items) {
const itemPath = path_1.default.join(dir, item);
const stats = fs_1.default.statSync(itemPath);
if (stats.isDirectory()) {
const result = searchDirectory(itemPath);
if (result)
return result;
}
else if (item.endsWith(".json") && !item.endsWith(".dbg.json")) {
try {
const artifact = JSON.parse(fs_1.default.readFileSync(itemPath, "utf8"));
if (artifact.deployedBytecode &&
artifact.deployedBytecode.toLowerCase() ===
targetBytecode.toLowerCase()) {
const contractName = path_1.default.basename(item, ".json");
return { contractName, artifactPath: itemPath };
}
}
catch (error) {
continue;
}
}
}
return null;
};
return searchDirectory(artifactsPath);
}
}
exports.ContractDataExtractor = ContractDataExtractor;
//# sourceMappingURL=contractDataExtractor.js.map