UNPKG

@coinmeca/ethers

Version:

Solidty helpers and utilities for using ethers.

476 lines (475 loc) 21.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FacetCutAction = void 0; exports.createArtifact = createArtifact; exports.createInfo = createInfo; exports.abi = abi; exports.cut = cut; exports.upgrade = upgrade; exports.cutMultiple = cutMultiple; exports.cutAdd = cutAdd; exports.cutReplace = cutReplace; exports.cutRemove = cutRemove; exports.factory = factory; exports.getAllFunctionNames = getAllFunctionNames; exports.getAllFunctionSelectors = getAllFunctionSelectors; exports.getSelectors = getSelectors; exports.getSelector = getSelector; exports.removeSelectors = removeSelectors; exports.compile = compile; const fs_1 = require("fs"); const crypto_1 = require("crypto"); const hardhat_1 = require("hardhat"); const config_1 = require("hardhat/config"); const resolver_1 = require("hardhat/internal/solidity/resolver"); const compilation_job_1 = require("hardhat/internal/solidity/compilation-job"); const task_names_1 = require("hardhat/builtin-tasks/task-names"); const utils_1 = require("./utils"); exports.FacetCutAction = { Add: 0, Replace: 1, Remove: 2 }; const diamondConfig = require(`${process.cwd()}/diamond.config.ts`).default; class abiCompilationJob extends compilation_job_1.CompilationJob { _file; artifacts = []; constructor(directory, name) { // Dummy solidity version that can never be valid super({ version: 'X.X.X', settings: {} }); // "sourceName": "contracts/app/App.sol" const sourceName = `${directory}/${name}.sol`; const absolutePath = directory; const content = { rawContent: '', imports: [], versionPragmas: [] }; // Dummy a content hash with the plugin name & version const contentHash = (0, crypto_1.createHash)('md5').update(`${directory}/${name}.sol`).digest('hex'); const lastModificationDate = new Date(); this._file = new resolver_1.ResolvedFile(sourceName, absolutePath, content, contentHash, lastModificationDate); } emitsArtifacts() { return true; } hasSolc9573Bug() { return false; } getResolvedFiles() { return [this._file]; } getFile() { return this._file; } addArtifact(artifact) { this.artifacts.push(artifact); } getArtifactsEmitted() { return this.artifacts.map((artifact) => `${artifact.contractName}.sol`); } } // function diamondConfigValidation() { // [diamondConfig].flat().map(function (config) { // const { diamond, include, exclude, filter } = config ?? {}; // if (diamond && !Array.isArray(diamond)) { // throw new HardhatPluginError(`abi`, '`diamond` config must be a string.'); // } // if (include && !Array.isArray(include)) { // throw new HardhatPluginError(`abi`, '`include` config must be an array if provided.'); // } // if (exclude && !Array.isArray(exclude)) { // throw new HardhatPluginError(`abi`, '`exclude` config must be an array if provided.'); // } // if (filter && typeof filter !== 'function') { // throw new HardhatPluginError(`abi`, '`filter` config must be a function if provided.'); // } // return { // include, // exclude, // filter // }; // } // }; async function __createArtifact(contract, facets) { let info; let path; let folder; let directory; let name; const diamond = (await hardhat_1.artifacts.getAllFullyQualifiedNames()).filter((f) => f.includes(contract)).filter((f) => f.includes(`${diamondConfig.artifact?.abi?.file || '.diamond'}`)); const artifactsList = (await hardhat_1.artifacts.getAllFullyQualifiedNames()); const artifactName = contract + (contract.includes(':') ? '' : '.sol:' + contract); const artifactNames = artifactsList.filter((f) => f.includes(artifactName)).filter((f) => { let path = diamondConfig?.artifact?.abi?.path; path = path ? (path?.startsWith('artifacts/') && path?.length > 10 ? path?.substring(10, path.length) : path) : undefined; return !f.includes(path || '.diamonds') || !f.includes(`.${diamondConfig.artifact?.abi?.file || 'diamond'}`); }).filter((c) => ((diamondConfig?.artifact?.abi?.include && diamondConfig?.artifact?.abi?.include?.length > 0 && diamondConfig?.artifact?.abi?.include?.find((f) => c?.includes(f))) ? false : c)); if (diamond?.length === 1 && !facets) { info = await hardhat_1.artifacts.readArtifact(diamond[0]); path = diamond[0].split('/'); folder = path.length; directory = path.slice(0, folder - 1).join('/'); name = path[folder - 1].split(':')[1].replaceAll(`.${diamondConfig.artifact?.abi?.file || 'diamond'}`, ''); } else { if (artifactNames.length == 0) throw console.error(utils_1.color.lightGray(`\nDiamondArtifactsError: There is no diamond contract for the given name.`)); if (artifactNames.length > 1) throw console.error(utils_1.color.lightGray(`\nDiamondArtifactsError: Couldn't create diamond artifacts via config or there are multiple contracts with the same name.\n\n${contract}:\n${artifactNames .map((a) => ` - ${a}`) .join('\n')} \n`)); path = artifactNames[0].split('/'); folder = path.length; directory = path.slice(0, folder - 1).join('/'); name = path[folder - 1].split(':')[1]; } if (!facets) { const config = diamondConfig?.artifact?.abi; const include = config?.include || ['facet', 'facets', 'shared']; const exclude = [`I${contract} `, ...(config?.exclude || [])]; const base = artifactsList.filter((f) => !f.includes(artifactName) && f.includes(directory)); facets = base.filter((f) => { for (const n of include) { return f.includes(n); } }); const others = facets.filter((f) => { const other = f.split(directory + '/')[1].split('/'); for (const n of include) { return !other[0].includes(n); } }); let inner = []; if (others.length > 0) { inner = base.filter((f) => { for (const n of include) { return !f.includes(n); } }); for (const e of exclude) { inner = inner.filter((f) => !f.includes(e)); } inner = inner.filter((f) => { const path = f.split('/'); !path[path.length - 1].startsWith('I'); }); } if (inner.length > 0) { facets = base.filter((f) => { for (const n of include) { return f.includes([directory, n].join('/')); } }); } } info = info ?? await hardhat_1.artifacts.readArtifact(path.join('/')); return { name, path, directory, facets, info }; } function createArtifact(artifact, abi) { const abis = [...artifact.info.abi, ...abi]; abi = []; for (let i = 0; i < abis.length; i++) { abi = abi.filter((abi) => JSON.stringify(abis[i]).trim() !== JSON.stringify(abi).trim()); abi.push(abis[i]); } const path = diamondConfig?.artifact?.abi?.path?.startsWith('artifacts') ? diamondConfig?.artifact?.abi?.path?.replace('artifacts', '') : diamondConfig?.artifact?.abi?.path; return { ...artifact.info, contractName: `${artifact.name}.${diamondConfig.artifact?.abi?.file || 'diamond'}`, sourceName: `${path || '.diamonds'}/${artifact.name}.sol`, abi: Array.from(new Set(abi)) }; } async function createInfo(diamond) { const contract = await __createArtifact(diamond.name); const path = `${diamondConfig.loupe?.path || 'artifacts/.diamonds'}/${contract.name}.sol`; const file = `${path}/${contract.name}.${diamondConfig.loupe?.file || 'facets'}.json`; (0, fs_1.writeFile)(file, JSON.stringify(diamond, null, 2), async (e) => { if (e?.message.includes('no such file or directory')) { (0, fs_1.mkdirSync)(path, { recursive: true }); await createInfo(diamond); } else if (e) { console.error(e); } }); return file; } async function abi(name, facets) { const diamondArtifact = await __createArtifact(name, facets); const compilationJob = new abiCompilationJob(diamondArtifact.directory, diamondArtifact.name); const abis = []; for (const facet of diamondArtifact.facets) { const { abi } = await hardhat_1.artifacts.readArtifact(facet); abis.push(...abi.filter((abiElement, index, abi) => { if (abiElement.type === 'constructor') { return false; } if (abiElement.type === 'fallback') { return false; } if (abiElement.type === 'receive') { return false; } if (typeof diamondConfig?.artifact?.abi?.filter === 'function') { return diamondConfig?.artifact?.abi?.filter(abiElement, index, abi, facet); } return true; })); } const diamond = createArtifact(diamondArtifact, abis); await hardhat_1.artifacts.saveArtifactAndDebugFile(diamond); compilationJob.addArtifact(diamond); compilationJob.getFile(); compilationJob.getArtifactsEmitted(); return diamond.contractName; } async function cut(cuts, display, name) { const diamond = []; for (let i = 0; i < cuts.length; i++) { let data = []; if (display) { console.log(utils_1.color.lightGray(`---------------------------------------------------------------`)); console.log(utils_1.color.lightGray((0, utils_1._)(`Key:`, 14)), utils_1.font.bold(utils_1.color.white(cuts[i].key))); console.log(utils_1.color.lightGray(`---------------------------------------------------------------`)); } for (const cut of cuts[i].data) { const facet = typeof cut === 'string' ? cut?.startsWith('0x') ? cut : await (await hardhat_1.ethers.getContractFactory(cut)).deploy() : typeof cut === 'object' && (cut?.facet?.startsWith('0x') ? cut?.facet : await (await hardhat_1.ethers.getContractFactory(cut?.facet)).deploy()); const address = typeof facet === 'object' ? await facet.getAddress() : facet; const selectors = typeof cut === 'object' ? (Array.isArray(cut?.selectors) ? typeof cut?.selectors[0] === 'object' ? [ ...getSelectors(facet), ...((cut?.selectors).map((c) => (!c?.action || c?.action === exports.FacetCutAction.Add) && c?.selectors?.filter((a) => !getSelectors(facet).includes(a))) .flatMap((a) => a) .filter(s => s)) ] .filter((f) => cut?.selectors?.filter((c) => c?.action === exports.FacetCutAction.Remove)?.filter((c) => c?.selectors.includes(f)).length == 0) : cut.selectors || getSelectors(facet) : typeof cut?.selectors === 'object' ? [ ...getSelectors(facet), ...(!cut?.selectors?.action || cut?.selectors?.action === exports.FacetCutAction.Add ? cut?.selectors?.selectors?.filter((a) => !getSelectors(facet).includes(a)) : []) ] .filter(f => !(cut?.selectors?.action === exports.FacetCutAction.Remove && cut?.selectors?.selectors?.includes(f))) : cut.selectors || getSelectors(facet)) : getSelectors(facet); if (display) { console.log(utils_1.color.lightGray((0, utils_1._)(`Facet:`, 14)), typeof cut === 'object' && typeof cut?.facet === 'string' ? cut?.facet : cut); console.log(utils_1.color.lightGray((0, utils_1._)(`Address:`, 14)), address); console.log(utils_1.color.lightGray((0, utils_1._)(`Selectors:`, 14)), selectors); console.log(utils_1.color.lightGray(`---------------------------------------------------------------`)); } data.push({ action: (typeof cut === 'object' && cut?.action) || exports.FacetCutAction.Add, facetAddress: address, functionSelectors: selectors }); } diamond.push({ key: cuts[i].key, data: data }); } return diamond; } async function upgrade(contract, cuts, init, display) { const deployer = (typeof init === 'object' ? init?.owner : undefined) || diamondConfig?.deployer?.address; const cutFacet = diamondConfig?.cut; if (!deployer) throw new Error(utils_1.color.red(`DiamondUpgradeError: There is no deployer configured.`)); if (!cutFacet) throw new Error(utils_1.color.red(`DiamondUpgradeError: There is no cut facet contract configured.`)); const cutContract = (typeof contract === 'string' && contract.startsWith('0x')) ? await hardhat_1.ethers.getContractAt(cutFacet, contract) : contract; return await cutContract.diamondCut(await cut(cuts, typeof init === 'boolean' ? init : display), { owner: (typeof init === 'object' && init?.owner) || deployer, init: (typeof init === 'object' && init?.init) || (0, utils_1.a)(0), initCalldata: (typeof init === 'object' && init?.initCalldata) || (0, utils_1.a)(0) }); } async function cutMultiple(facetCuts) { const diamondCut = []; for (let i = 0; i < facetCuts.length; i++) { diamondCut.push({ key: facetCuts[i].key, data: facetCuts }); } return diamondCut; } function cutAdd(key, facet, selectors) { return [{ key: key, data: [{ action: exports.FacetCutAction.Add, facet, selectors }] }]; } function cutReplace(key, facet, selectors) { return [{ key: key, data: [{ action: exports.FacetCutAction.Replace, facet, selectors }] }]; } function cutRemove(key, facet, selectors) { return [{ key: key, data: [{ action: exports.FacetCutAction.Remove, facet, selectors }] }]; } async function factory(name, args, display) { let diamond = { name }; let facets = []; for (let i = 0; i < args.length; i++) { if (Array.isArray(args[i]) && 'key' in args[i][0] && 'data' in args[i][0]) { if (Array.isArray(args[i][0]?.data) && args[i][0]?.data.length > 0 && (typeof args[i][0].data[0] === 'string' || (typeof args[i][0].data[0] === 'object' && typeof args[i][0].data[0]?.facet === 'string'))) { facets = args[i].flatMap((c) => c?.data?.map((c) => c?.facet || c)); args[i] = await cut(args[i], display); } diamond = { ...diamond, facets: args[i] }; } if (typeof args[i] === 'object' && 'owner' in args[i] && 'init' in args[i] && 'initCalldata' in args[i]) diamond = { ...diamond, init: args[i] }; } let contract; try { const artifact = name + '.diamond'; contract = await hardhat_1.ethers.getContractFactoryFromArtifact(await hardhat_1.artifacts.readArtifact(`${artifact.includes(':') ? artifact.split(':')[1] : artifact.includes('/') ? artifact.split('/')[artifact.split('/').length - 1] : artifact}`)); } catch (error) { if (JSON.stringify(error).includes('not found')) { try { contract = await hardhat_1.ethers.getContractFactory(await abi(name, facets)); } catch { try { contract = await hardhat_1.ethers.getContractFactory(`${name}.${diamondConfig.artifact?.abi?.file || 'diamond'}`); } catch (e) { throw console.error(utils_1.color.lightGray(`\nDiamondArtifactsError: Cannot find diamond artifact.\n\n${e}`)); } } } } const deployed = await contract.deploy(...args); await createInfo({ name: diamond.name, address: await deployed.getAddress(), facets: diamond.facets, init: diamond.init }); return deployed; } function getAllFunctionNames(contract) { return (contract?.contract?.interface || contract?.interface).format(true).filter((f) => f.startsWith('function')); } function getAllFunctionSelectors(contract, display) { contract = contract?.contract?.interface ? contract?.contract : contract; if (display) console.log(utils_1.color.lightGray(`----------------------------- Contract Functions -----------------------------`)); return getAllFunctionNames(contract).map((f) => { const selector = contract.interface.getFunction(f)?.selector; if (display) console.log(`Function: ${f}`); if (display) console.log(`Selector: ${selector}`); if (display) console.log(utils_1.color.lightGray(`--------------------------------------------------------------------------------`)); return selector; }); } function get(functionNames) { const contract = this?.contract?.interface ? this?.contract : this; let selectors = []; if (functionNames) { selectors = functionNames.map((n) => { console.log('callbefore', contract); const names = getAllFunctionNames(contract).filter(f => f.includes(n)).map(f => { return contract.interface.getFunction(f.split(' ')[1])?.selector; }); const sigs = getAllFunctionSelectors(contract).filter(f => f === n); return names.length > 0 ? names : sigs; }).flat(); } else { selectors = getAllFunctionSelectors(contract); } this.selectors = selectors; return selectors; } function remove(functionNames) { const contract = this?.contract?.interface ? this?.contract : this; const selectors = contract?.selectors?.filter((s) => { const names = functionNames.filter(f => getSelector(f) === s).includes(s); const sigs = functionNames.includes(s); return !names && !sigs; }).flat() || []; this.selectors = selectors; return selectors; } function getSelectors(contract) { const wrapping = contract?.contract?.interface ? true : false; const selectors = getAllFunctionSelectors(wrapping ? contract?.contract : contract); if (wrapping) { contract.contract.selectors = selectors; contract.contract.get = get; contract.contract.remove = remove; } else { contract.selectors = selectors; contract.get = get; contract.remove = remove; } return selectors; } function getSelector(functionName) { const fragment = new hardhat_1.ethers.Interface('function ' + functionName); return fragment.getFunction(functionName)?.selector; } function removeSelectors(selectors, functionNames) { const type = functionNames.filter((f) => f.startsWith('0x')); let removedSelectors; if (type.length === 0) { functionNames = functionNames.map((functionName) => 'function ' + functionName); const fragments = new hardhat_1.ethers.Interface(functionNames); removedSelectors = selectors.filter((s) => { return !functionNames.map((f) => fragments.getFunction(f)?.selector).filter(f => f === s).includes(s); }); } else { removedSelectors = selectors.filter((s) => { return !functionNames.includes(s); }); } return removedSelectors; } async function compile(args, hre, runSuper) { const out = await runSuper(args); if (out.artifactsEmittedPerJob.length === 0) { return out; } const diamonds = diamondConfig?.artifact?.diamonds; if (Array.isArray(diamonds) && diamonds?.length > 0) { for (const diamond of diamonds) { const diamondArtifact = await __createArtifact(diamond, await hre.artifacts.getAllFullyQualifiedNames()); const compilationJob = new abiCompilationJob(diamondArtifact.directory, diamondArtifact.name); const file = compilationJob.getFile(); const artifactsEmitted = compilationJob.getArtifactsEmitted(); return { artifactsEmittedPerJob: [ ...out.artifactsEmittedPerJob, // Add as another job to the list { compilationJob, artifactsEmittedPerFile: [ { file, artifactsEmitted } ] } ] }; } } } // We ONLY hook this task, instead of providing a separate task to run, because // Hardhat will clear out old artifacts on next run if we don't work around their // caching mechanisms. (0, config_1.subtask)(task_names_1.TASK_COMPILE_SOLIDITY_COMPILE_JOBS).setAction(compile);