zksync-cli
Version:
CLI tool that simplifies the process of developing applications and interacting with the ZKsync network
188 lines • 7.3 kB
JavaScript
import chalk from "chalk";
import { ethers } from "ethers";
import fs from "fs";
import inquirer from "inquirer";
import ora from "ora";
import { getMethodId } from "./formatters.js";
import { getProxyImplementation } from "./proxy.js";
import { fileOrDirExists } from "../../../utils/files.js";
import { formatSeparator } from "../../../utils/formatters.js";
import Logger from "../../../utils/logger.js";
export const formatMethodString = (method) => {
// remove "function " prefix and return type
// e.g. "greet() view returns (string)" -> "greet()"
return method.substring("function ".length).replace(/\).+$/, ")");
};
export const getMethodsFromAbi = (abi, type) => {
const getReadMethods = () => {
const readMethods = abi.filter((fragment) => fragment.type === "function" && (fragment.stateMutability === "view" || fragment.stateMutability === "pure"));
const contractInterface = new ethers.utils.Interface(readMethods);
return contractInterface.fragments;
};
const getWriteMethods = () => {
const writeMethods = abi.filter((fragment) => fragment.type === "function" &&
(fragment.stateMutability === "nonpayable" || fragment.stateMutability === "payable"));
const contractInterface = new ethers.utils.Interface(writeMethods);
return contractInterface.fragments;
};
if (type === "read") {
return getReadMethods();
}
else if (type === "write") {
return getWriteMethods();
}
return [...getReadMethods(), ...getWriteMethods()];
};
export const checkIfMethodExists = (contractInfo, method) => {
const methodId = getMethodId(method);
if (!contractInfo.bytecode.includes(methodId)) {
if (!contractInfo.implementation) {
Logger.warn("Provided method is not part of the contract and will only work if provided contract is a proxy");
}
else if (!contractInfo.implementation.bytecode.includes(methodId)) {
Logger.warn("Provided method is not part of the provided contract nor its implementation");
}
}
};
export const getContractABI = async (chain, contractAddress) => {
if (!chain.verificationApiUrl)
return;
const response = await fetch(`${chain.verificationApiUrl}/contract_verification/info/${contractAddress}`);
const decoded = await response.json();
return decoded.artifacts.abi;
};
export const readAbiFromFile = (abiFilePath) => {
if (!fileOrDirExists(abiFilePath)) {
throw new Error(`ABI not found at specified location: ${abiFilePath}`);
}
const contents = fs.readFileSync(abiFilePath, "utf-8");
try {
const data = JSON.parse(contents);
if (Array.isArray(data)) {
return data;
}
else if (data?.abi) {
return data.abi;
}
throw new Error("ABI wasn't found in the provided file");
}
catch (error) {
throw new Error(`Failed to parse ABI file: ${error instanceof Error ? error.message : error}`);
}
};
export const getContractInformation = async (chain, provider, contractAddress, options) => {
const [bytecode, abi] = await Promise.all([
provider.getCode(contractAddress),
chain ? getContractABI(chain, contractAddress).catch(() => undefined) : undefined,
]);
const contractInfo = {
address: contractAddress,
bytecode,
abi,
};
if (options?.fetchImplementation) {
const implementationAddress = await getProxyImplementation(contractAddress, provider).catch(() => undefined);
if (implementationAddress) {
const implementation = await getContractInformation(chain, provider, implementationAddress);
contractInfo.implementation = implementation;
}
}
return contractInfo;
};
export const getContractInfoWithLoader = async (chain, provider, contractAddress) => {
const spinner = ora("Fetching contract information...").start();
try {
const contractInfo = await getContractInformation(chain, provider, contractAddress, { fetchImplementation: true });
if (contractInfo.bytecode === "0x") {
throw new Error("Provided address is not a contract");
}
return contractInfo;
}
finally {
spinner.stop();
}
};
export const askAbiMethod = async (contractInfo, type = "any") => {
if (!contractInfo.abi && !contractInfo.implementation?.abi) {
return "manual";
}
const formatFragment = (fragment) => {
let name = fragment.format(ethers.utils.FormatTypes.full);
if ((type === "write" || type === "any") && name.includes(" returns ")) {
name = name.substring(0, name.indexOf(" returns ")); // remove return type for write methods
}
return {
name: name.substring("function ".length),
value: fragment,
};
};
const choices = [];
const separators = {
noReadMethods: { type: "separator", line: chalk.white("No read methods found") },
noWriteMethods: { type: "separator", line: chalk.white("No write methods found") },
noMethods: { type: "separator", line: chalk.white("No methods found") },
contractNotVerified: { type: "separator", line: chalk.white("Contract is not verified") },
};
choices.push(formatSeparator("Provided contract"));
if (contractInfo.abi) {
const methods = getMethodsFromAbi(contractInfo.abi, type);
if (methods.length) {
choices.push(...methods.map(formatFragment));
}
else {
if (type === "read") {
choices.push(separators.noReadMethods);
}
else if (type === "write") {
choices.push(separators.noWriteMethods);
}
else {
choices.push(separators.noMethods);
}
}
}
else {
choices.push(separators.contractNotVerified);
}
if (contractInfo?.implementation) {
if (contractInfo.implementation.abi) {
choices.push(formatSeparator("Resolved implementation"));
const implementationMethods = getMethodsFromAbi(contractInfo.implementation.abi, type);
if (implementationMethods.length) {
choices.push(...implementationMethods.map(formatFragment));
}
else {
if (type === "read") {
choices.push(separators.noReadMethods);
}
else if (type === "write") {
choices.push(separators.noWriteMethods);
}
else {
choices.push(separators.noMethods);
}
}
}
else {
choices.push(separators.contractNotVerified);
}
}
choices.push(formatSeparator(""));
choices.push({
name: "Type method manually",
value: "manual",
});
const { method } = await inquirer.prompt([
{
message: "Contract method to call",
name: "method",
type: "list",
choices,
required: true,
pageSize: 10,
loop: false,
},
]);
return method;
};
//# sourceMappingURL=helpers.js.map