UNPKG

@moonwall/cli

Version:

Testing framework for the Moon family of projects

261 lines (258 loc) 7.56 kB
// src/lib/contractFunctions.ts import { ALITH_PRIVATE_KEY, PRECOMPILES, createEthersTransaction, createViemTransaction, deployViemContract } from "@moonwall/util"; import chalk from "chalk"; import { Interface, Wallet } from "ethers"; import fs, { readFileSync as readFileSync2 } from "fs"; import path2 from "path"; import { decodeFunctionResult, encodeFunctionData, toHex } from "viem"; import { privateKeyToAccount } from "viem/accounts"; // src/lib/configReader.ts import "@moonbeam-network/api-augment"; import { readFile, access } from "fs/promises"; import { readFileSync, existsSync, constants } from "fs"; import JSONC from "jsonc-parser"; import path, { extname } from "path"; var cachedConfig; function parseConfigSync(filePath) { let result; const file = readFileSync(filePath, "utf8"); switch (extname(filePath)) { case ".json": result = JSON.parse(file); break; case ".config": result = JSONC.parse(file); break; default: result = void 0; break; } return result; } function importJsonConfig() { if (cachedConfig) { return cachedConfig; } const configPath = process.env.MOON_CONFIG_PATH; if (!configPath) { throw new Error("No moonwall config path set. This is a defect, please raise it."); } const filePath = path.isAbsolute(configPath) ? configPath : path.join(process.cwd(), configPath); try { const config = parseConfigSync(filePath); const replacedConfig = replaceEnvVars(config); cachedConfig = replacedConfig; return cachedConfig; } catch (e) { console.error(e); throw new Error(`Error import config at ${filePath}`); } } function replaceEnvVars(value) { if (typeof value === "string") { return value.replace(/\$\{([^}]+)\}/g, (match, group) => { const envVarValue = process.env[group]; return envVarValue || match; }); } if (Array.isArray(value)) { return value.map(replaceEnvVars); } if (typeof value === "object" && value !== null) { return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, replaceEnvVars(v)])); } return value; } // src/lib/contractFunctions.ts function getCompiledPath(contractName) { const config = importJsonConfig(); const contractsDir = config.environments.find( (env) => env.name === process.env.MOON_TEST_ENV )?.contracts; if (!contractsDir) { throw new Error( `Contracts directory not found for environment config ${process.env.MOON_TEST_ENV} Please specify path to Foundry directory at: ${chalk.bgWhiteBright.blackBright( "moonwall.config.json > environments > contracts" )}` ); } const compiledJsonPath = recursiveSearch(contractsDir, `${contractName}.json`); const solidityFilePath = recursiveSearch(contractsDir, `${contractName}.sol`); if (!compiledJsonPath && !solidityFilePath) { throw new Error( `Neither solidity contract ${contractName}.sol nor its compiled json exists in ${contractsDir}` ); } if (!compiledJsonPath) { throw new Error( `Compiled contract ${contractName}.json doesn't exist Please ${chalk.bgWhiteBright.blackBright("recompile contract")} ${contractName}.sol` ); } return compiledJsonPath; } function fetchCompiledContract(contractName) { const compiledPath = getCompiledPath(contractName); const json = readFileSync2(compiledPath, "utf8"); const parsed = JSON.parse(json); return { abi: parsed.contract.abi, bytecode: parsed.byteCode, methods: parsed.contract.evm.methodIdentifiers, deployedBytecode: `0x${parsed.contract.evm.deployedBytecode.object}` }; } function recursiveSearch(dir, filename) { const files = fs.readdirSync(dir); for (const file of files) { const filepath = path2.join(dir, file); const stats = fs.statSync(filepath); if (stats.isDirectory()) { const searchResult = recursiveSearch(filepath, filename); if (searchResult) { return searchResult; } } else if (stats.isFile() && file === filename) { return filepath; } } return null; } async function interactWithPrecompileContract(context, callOptions) { const { precompileName, ...rest } = callOptions; const precompileInfo = PRECOMPILES[precompileName]; if (!precompileInfo) { throw new Error(`No precompile found with the name: ${precompileName}`); } const [contractAddress, contractName] = Array.isArray(precompileInfo) ? precompileInfo : [precompileInfo, precompileName]; return await interactWithContract(context, { ...rest, contractName, contractAddress }); } async function interactWithContract(context, callOptions) { const { contractName, contractAddress, functionName, args = [], web3Library = "viem", gas = "estimate", value = 0n, privateKey = ALITH_PRIVATE_KEY, rawTxOnly = false, call = false } = callOptions; const { abi } = fetchCompiledContract(contractName); const data = encodeFunctionData({ abi, functionName, args }); const account = privateKeyToAccount(privateKey); const gasParam = gas === "estimate" ? await context.viem().estimateGas({ account: account.address, to: contractAddress, value: 0n, data }) : gas > 0n ? gas : 200000n; if (!call && rawTxOnly) { return web3Library === "viem" ? createViemTransaction(context, { to: contractAddress, data, gas: gasParam, privateKey, value }) : createEthersTransaction(context, { to: contractAddress, data, gas: gasParam, value: toHex(value), privateKey }); } if (call) { if (web3Library === "viem") { const result2 = await context.viem().call({ account: account.address, to: contractAddress, value: 0n, data, gas: gasParam }); if (!result2.data) { throw new Error("No data field returned from call"); } return decodeFunctionResult({ abi, functionName, data: result2.data }); } const result = await context.ethers().call({ from: account.address, to: contractAddress, value: toHex(value), data, gasLimit: toHex(gasParam) }); return new Interface(abi).decodeFunctionResult(functionName, result); } if (!rawTxOnly) { if (web3Library === "viem") { const hash2 = await context.viem().sendTransaction({ account, to: contractAddress, value, data, gas: gasParam }); return hash2; } const signer = new Wallet(privateKey, context.ethers().provider); const { hash } = await signer.sendTransaction({ from: account.address, to: contractAddress, value: toHex(value), data, gasLimit: toHex(gasParam) }); return hash; } throw new Error("This should never happen, if it does there's a logic error in the code"); } async function deployCreateCompiledContract(context, contractName, options) { const { abi, bytecode, methods } = fetchCompiledContract(contractName); const { privateKey = ALITH_PRIVATE_KEY, args = [], ...rest } = options || {}; const blob = { ...rest, privateKey, args }; const { contractAddress, logs, status, hash } = await deployViemContract( context, abi, bytecode, blob ); return { contractAddress, logs, hash, status, abi, bytecode, methods }; } export { deployCreateCompiledContract, fetchCompiledContract, interactWithContract, interactWithPrecompileContract, recursiveSearch };