@coinmeca/ethers
Version:
Solidty helpers and utilities for using ethers.
476 lines (475 loc) • 21.5 kB
JavaScript
;
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);