diff --git a/axelar-chains-config/info/mainnet.json b/axelar-chains-config/info/mainnet.json index ce2768da..2e50e989 100644 --- a/axelar-chains-config/info/mainnet.json +++ b/axelar-chains-config/info/mainnet.json @@ -1172,7 +1172,8 @@ "confirmations": 3, "explorer": { "name": "Filfox", - "url": "https://www.filfox.info" + "url": "https://www.filfox.info", + "api": "https://filfox.info/api/v1/tools/verifyContract" }, "gasOptions": { "gasLimit": 500000000 diff --git a/axelar-chains-config/info/testnet.json b/axelar-chains-config/info/testnet.json index 32a8d5d0..8cfd5e2e 100644 --- a/axelar-chains-config/info/testnet.json +++ b/axelar-chains-config/info/testnet.json @@ -1276,7 +1276,8 @@ }, "explorer": { "name": "Filfox", - "url": "https://calibration.filfox.info" + "url": "https://calibration.filfox.info", + "api": "https://calibration.filfox.info/api/v1/tools/verifyContract" }, "gasOptions": { "gasLimit": 300000000 diff --git a/axelar-chains-config/package-lock.json b/axelar-chains-config/package-lock.json index 661a42fb..204f5c93 100644 --- a/axelar-chains-config/package-lock.json +++ b/axelar-chains-config/package-lock.json @@ -13,8 +13,10 @@ "fs-extra": "^11.1.1" }, "devDependencies": { + "axios": "^1.6.2", "eslint": "^8.49.0", "jsonschema": "^1.4.1", + "path": "^0.12.7", "prettier": "^3.0.3", "typescript": "^5.2.2", "vitest": "^0.34.4" @@ -782,6 +784,23 @@ "node": "*" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -877,6 +896,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -932,6 +963,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -1218,6 +1258,40 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", @@ -1550,6 +1624,27 @@ "node": ">=12" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1672,6 +1767,16 @@ "node": ">=6" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dev": true, + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1809,6 +1914,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2112,6 +2232,21 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, "node_modules/vite": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", diff --git a/axelar-chains-config/package.json b/axelar-chains-config/package.json index 27694c8b..168e10ad 100644 --- a/axelar-chains-config/package.json +++ b/axelar-chains-config/package.json @@ -21,7 +21,9 @@ "jsonschema": "^1.4.1", "prettier": "^3.0.3", "typescript": "^5.2.2", - "vitest": "^0.34.4" + "vitest": "^0.34.4", + "path": "^0.12.7", + "axios": "^1.6.2" }, "dependencies": { "@ethersproject/keccak256": "^5.7.0", diff --git a/axelar-chains-config/src/utils/verifyContract.js b/axelar-chains-config/src/utils/verifyContract.js index 0924b6f5..b412269c 100644 --- a/axelar-chains-config/src/utils/verifyContract.js +++ b/axelar-chains-config/src/utils/verifyContract.js @@ -1,5 +1,139 @@ const { execSync } = require('child_process'); const { writeFileSync } = require('fs'); +const fs = require('fs'); +const path = require('path'); +const axios = require('axios'); + +async function getSmallestFile(directoryPath) { + const fileNames = await fs.promises.readdir(directoryPath); + let smallestFile = null; + let smallestSize = Number.MAX_SAFE_INTEGER; + + for (const fileName of fileNames) { + const filePath = path.join(directoryPath, fileName); + const stats = await fs.promises.stat(filePath); + + if (stats.size < smallestSize) { + smallestFile = filePath; + smallestSize = stats.size; + } + } + + return smallestFile; +} + +function getContractFileName(config, address) { + for (const [key, currentValue] of Object.entries(config)) { + if (currentValue === address) { + return key.charAt(0).toUpperCase() + key.slice(1); + } + } + + return null; +} + +async function findFilePath(startPath, targetFileName) { + if (!fs.existsSync(startPath)) { + throw new Error(`Start path does not exist: ${startPath}`); + } + + const directoriesToSearch = [startPath]; + + while (directoriesToSearch.length > 0) { + const currentDirectory = directoriesToSearch.pop(); + const filesAndDirectories = fs.readdirSync(currentDirectory, { withFileTypes: true }); + + for (const fileOrDirectory of filesAndDirectories) { + const fullPath = path.join(currentDirectory, fileOrDirectory.name); + + if (fileOrDirectory.isDirectory()) { + directoriesToSearch.push(fullPath); + } else if (fileOrDirectory.isFile() && fileOrDirectory.name === targetFileName) { + return fullPath; + } + } + } + + throw new Error(`File not found: ${targetFileName}`); +} + +async function verifyFilfox(env, contract, options) { + const { dir, contractName } = options; + + if (!dir || !contractName) { + throw new Error('Invalid verification options'); + } + + const config = require(`../../info/${env}.json`); + const contractConfig = config.chains.filecoin.contracts[contractName]; + const contractFileName = getContractFileName(contractConfig, contract); + + const contractPath = await findFilePath(dir, `${contractFileName}.sol`); + + const sourceFiles = { + [`${contractFileName}.sol`]: { + content: fs.readFileSync(contractPath, 'utf8'), + }, + }; + + let buildInfo; + + try { + const buildInfoPath = path.join(dir, 'build-info'); + const smallestFile = await getSmallestFile(buildInfoPath); + + if (!smallestFile) { + throw new Error('No build info files found'); + } + + const buildInfoContent = fs.readFileSync(smallestFile, 'utf8'); + buildInfo = JSON.parse(buildInfoContent); + } catch (error) { + console.error('Error reading contract build info', error); + } + + const language = buildInfo.input?.language; + const compiler = buildInfo.solcLongVersion; + const settings = buildInfo.input?.settings; + + const optimize = settings.optimizer?.enabled || true; + const optimizeRuns = settings.optimizer?.runs; + const optimizerDetails = settings.optimizer?.details || ''; + const evmVersion = settings.evmVersion; + + const data = { + address: contract, + language, + compiler, + optimize, + optimizeRuns, + optimizerDetails, + sourceFiles, + license: 'MIT License (MIT)', + evmVersion, + viaIR: false, + libraries: '', + metadata: '', + }; + + const api = config.chains.filecoin.explorer?.api; + + if (!api) { + throw new Error(`Explorer API not present for filecoin ${env}`); + } + + try { + const response = await axios.post(api, data, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + console.log('Verification response:', JSON.stringify(response.data)); + } catch (error) { + console.error('Error during verification:', JSON.stringify(error.response.data)); + } +} /** * Verifies a contract on etherscan-like explorer of the provided chain using hardhat. @@ -12,7 +146,12 @@ const { writeFileSync } = require('fs'); * @param {any[]} args * @returns {void} */ -const verifyContract = (env, chain, contract, args, options = {}) => { +const verifyContract = async (env, chain, contract, args, options = {}) => { + if (chain.toLowerCase() === 'filecoin') { + await verifyFilfox(env, contract, options); + return; + } + const stringArgs = args.map((arg) => JSON.stringify(arg)); const content = `module.exports = [\n ${stringArgs.join(',\n ')}\n];`; const file = 'temp-arguments.js'; diff --git a/evm/verify-contract.js b/evm/verify-contract.js index 37c76ed2..fb122bd9 100644 --- a/evm/verify-contract.js +++ b/evm/verify-contract.js @@ -18,7 +18,9 @@ async function processCommand(config, chain, options) { const { env, contractName, dir } = options; const provider = getDefaultProvider(chain.rpc); const wallet = Wallet.createRandom().connect(provider); - const verifyOptions = {}; + const verifyOptions = { + contractName, + }; if (dir) { verifyOptions.dir = dir;