Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: verify contracts on filfox #151

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion axelar-chains-config/info/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion axelar-chains-config/info/testnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
135 changes: 135 additions & 0 deletions axelar-chains-config/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion axelar-chains-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment on lines +25 to +26
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these aren't dev dependencies since it's for an exported function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I didn't have them as dependencies axelar chains config tests were failing, should I move them from dev dependencies into normal dependencies?

},
"dependencies": {
"@ethersproject/keccak256": "^5.7.0",
Expand Down
141 changes: 140 additions & 1 deletion axelar-chains-config/src/utils/verifyContract.js
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: if we convert fileOrDirectory.name and targetFileName to lowercase and compare them we should be able to simplify getContractFileName by removing slicing and first letter conversion to uppercase, which should increase code readability.

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't contractName already being passed in? you can enforce it to be passed in, and update usages in our code instead of the limited reverse map

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config map has different names for addresses than actual contract file names, so this should fail @deanamiel.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@milapsheth , @deanamiel can we modify all verifyContract() calls withinverify-contract.js to have a hardcoded contract name with it, which will simplify script execution for all the contracts. I will also be able to use this in my PR: #162

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contractName being passed would just be InterchainTokenService whereas I need each individual contract name within ITS to find the correct contract path to the flattened source code. @blockchainguyy yeah that is true, it will not work for a few contract names, I thought of adding an optional argument for the hardcoded contract name to verifyContract() as well but am not sure it's worth all these changes for only filfox verification for their specific verification endpoint requirements, thoughts @milapsheth ?


const contractPath = await findFilePath(dir, `${contractFileName}.sol`);

const sourceFiles = {
[`${contractFileName}.sol`]: {
content: fs.readFileSync(contractPath, 'utf8'),
},
};

let buildInfo;

try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move try/catch to be specifically over the code that can fail

const buildInfoPath = path.join(dir, 'build-info');
const smallestFile = await getSmallestFile(buildInfoPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the logic behind getting the smallest one? non-obvious behaviour should be commented

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that there are often multiple build files produced within the build-info folder, some are extremely large and unfeasible to parse just for compiler/optimize information but all seem to contain the same config information which is why I added a function to just parse the smallest file present. I agree it is a bit convoluted but I'm not sure how else to go about it


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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get the API from hardhat network or options.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.
Expand All @@ -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';
Expand Down
4 changes: 3 additions & 1 deletion evm/verify-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading