UNPKG

dop-stick

Version:

Source control tooling for versionable-upgradeable smart contracts

333 lines 18.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.diamondHelper = exports.DiamondHelper = void 0; const ethers_1 = require("ethers"); const logger_1 = require("./logsAndMetrics/core/logger"); const hardhatHelpers_1 = require("./hardhatHelpers"); const diamond_1 = require("../types/diamond"); class DiamondHelper { constructor() { } static getInstance() { if (!DiamondHelper.instance) { DiamondHelper.instance = new DiamondHelper(); } return DiamondHelper.instance; } getLoupeFunctions(config) { var _a, _b, _c; const loupeFacetConfig = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards) === null || _c === void 0 ? void 0 : _c.loupe; const customFunctions = loupeFacetConfig === null || loupeFacetConfig === void 0 ? void 0 : loupeFacetConfig.customFunctions; return { facets: this.getFunctionName(customFunctions === null || customFunctions === void 0 ? void 0 : customFunctions.facets) || DiamondHelper.DEFAULT_LOUPE_FUNCTIONS.facets, facetSelectors: this.getFunctionName(customFunctions === null || customFunctions === void 0 ? void 0 : customFunctions.facetSelectors) || DiamondHelper.DEFAULT_LOUPE_FUNCTIONS.facetSelectors, facetAddresses: this.getFunctionName(customFunctions === null || customFunctions === void 0 ? void 0 : customFunctions.facetAddresses) || DiamondHelper.DEFAULT_LOUPE_FUNCTIONS.facetAddresses, facetAddress: this.getFunctionName(customFunctions === null || customFunctions === void 0 ? void 0 : customFunctions.facetAddress) || DiamondHelper.DEFAULT_LOUPE_FUNCTIONS.facetAddress }; } getFunctionName(config) { var _a; if (!config) { return ''; } if (typeof config === 'string') { return config; } return ((_a = config.abi) === null || _a === void 0 ? void 0 : _a.toString()) || ''; } async checkSelectorCollisions(selectors, action, config) { try { const diamondAddress = process.env.DIAMOND_ADDRESS; if (!diamondAddress) { throw new Error('Diamond address not set'); } const loupeFunctions = this.getLoupeFunctions(config); const provider = await (0, hardhatHelpers_1.getProvider)(); const abiFormats = [ // Format 1: Standard tuple format `function ${loupeFunctions.facets}() view returns (tuple(address facetAddress, bytes4[] functionSelectors)[])`, // Format 2: Explicit struct format `function ${loupeFunctions.facets}() view returns ((address facetAddress, bytes4[] functionSelectors)[])`, // Format 3: Simple format `function ${loupeFunctions.facets}() view returns (address[] facetAddresses, bytes4[][] selectors)`, // Format 4: Legacy format `function ${loupeFunctions.facets}() view returns (address[],bytes4[][])` ]; let facets; let lastError = null; // Try each ABI format until one works for (const abi of abiFormats) { try { // logger.info(`Trying ABI format: ${abi}`); const diamond = new ethers_1.ethers.Contract(diamondAddress, [abi], provider); facets = await diamond[loupeFunctions.facets](); // logger.info(`Successfully called ${loupeFunctions.facets} with format: ${abi}`); break; } catch (e) { lastError = e instanceof Error ? e : new Error(String(e)); // logger.warning(`ABI format failed: ${lastError.message}`); continue; } } if (!facets) { throw new Error(`Failed to call ${loupeFunctions.facets} with all ABI formats. ` + `Last error: ${(lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'Unknown error'}`); } const collisions = []; const existingSelectors = new Map(); // Map all existing selectors if (Array.isArray(facets.facetAddresses)) { // Format 3 or 4 for (let i = 0; i < facets.facetAddresses.length; i++) { const address = facets.facetAddresses[i]; const selectorList = facets.selectors[i]; for (const selector of selectorList) { existingSelectors.set(selector.toLowerCase(), address); } } } else { // Format 1 or 2 for (const facet of facets) { for (const selector of facet.functionSelectors) { existingSelectors.set(selector.toLowerCase(), facet.facetAddress); } } } // Check for collisions with proper action handling for (const selector of selectors) { const normalizedSelector = selector.toLowerCase(); const exists = existingSelectors.has(normalizedSelector); const existingFacet = existingSelectors.get(normalizedSelector); // Log for debugging // logger.debug(`Checking selector: ${selector}`); // logger.debug(`Action: ${action}`); // logger.debug(`Exists: ${exists}`); // logger.debug(`Existing Facet: ${existingFacet}`); if ((action === diamond_1.DiamondCutAction.Add && exists) || (action === diamond_1.DiamondCutAction.Replace && !exists) || (action === diamond_1.DiamondCutAction.Remove && !exists)) { collisions.push({ selector: normalizedSelector, signature: await this.getFunctionSignature(selector, existingFacet), existingFacet }); } } return { hasCollisions: collisions.length > 0, collisions }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // logger.error(`Failed to check selector collisions: ${errorMessage}`); throw error; } } async validateDiamondOwnership(signer, config) { var _a, _b, _c, _d; try { const diamondAddress = process.env.DIAMOND_ADDRESS; if (!diamondAddress) { throw new Error('Diamond address not set'); } const provider = await (0, hardhatHelpers_1.getProvider)(); const ownershipConfig = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards) === null || _c === void 0 ? void 0 : _c.ownership; const ownerFunction = this.getFunctionName((_d = ownershipConfig === null || ownershipConfig === void 0 ? void 0 : ownershipConfig.customFunctions) === null || _d === void 0 ? void 0 : _d.owner) || DiamondHelper.DEFAULT_OWNER_FUNCTION; const diamond = new ethers_1.Contract(diamondAddress, [`function ${ownerFunction}() view returns (address)`], provider); const owner = await diamond[ownerFunction](); const signerAddress = await signer.getAddress(); if (owner.toLowerCase() !== signerAddress.toLowerCase()) { logger_1.Logger.error(`Signer ${signerAddress} is not the diamond owner ${owner}`); return false; } return true; } catch (error) { logger_1.Logger.error('Failed to validate diamond ownership'); return false; } } async estimateUpgradeGas(cuts, config) { var _a, _b, _c, _d; try { const diamondAddress = process.env.DIAMOND_ADDRESS; if (!diamondAddress) { throw new Error('Diamond address not set'); } // Get provider and signer const provider = await (0, hardhatHelpers_1.getProvider)(); if (!process.env.PRIVATE_KEY) { throw new Error('PRIVATE_KEY environment variable is not set'); } const signer = new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY, provider); // Create a dummy address for estimation const DUMMY_ADDRESS = process.env.DIAMOND_ADDRESS; // Replace address zero with dummy address for estimation const estimatableCuts = cuts.map(cut => ({ ...cut, facetAddress: cut.facetAddress === ethers_1.ethers.constants.AddressZero ? DUMMY_ADDRESS : cut.facetAddress })); // Only log if we actually used the dummy address // if (cuts.some(cut => cut.facetAddress === ethers.constants.AddressZero)) { // logger.info('Using dummy addresses for gas estimation'); // } const cutFacetConfig = (_c = (_b = (_a = config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards) === null || _c === void 0 ? void 0 : _c.cut; const customFunctions = cutFacetConfig === null || cutFacetConfig === void 0 ? void 0 : cutFacetConfig.customFunctions; const diamondCutConfig = customFunctions === null || customFunctions === void 0 ? void 0 : customFunctions.diamondCut; let functionName; let additionalArgs = []; let functionSignature = ''; if (typeof diamondCutConfig === 'string') { functionName = diamondCutConfig; functionSignature = `function ${functionName}(tuple(address facetAddress, uint8 action, bytes4[] functionSelectors)[] _diamondCut, address _init, bytes _calldata) external`; } else if (diamondCutConfig) { functionName = ((_d = diamondCutConfig.abi) === null || _d === void 0 ? void 0 : _d.toString()) || DiamondHelper.DEFAULT_CUT_FUNCTION; // Get additional parameters based on location if (diamondCutConfig.additionalParams) { additionalArgs = diamondCutConfig.additionalParams.map(param => { if (param.location === 'env') { const value = process.env[param.key]; if (!value) { throw new Error(`Missing environment variable ${param.key} for gas estimation`); } return value; } // Handle config location if needed return null; // Placeholder for config location handling }); // Build function signature with additional parameters const additionalParamTypes = diamondCutConfig.additionalParams .map(param => param.type) .join(', '); functionSignature = `function ${functionName}(tuple(address facetAddress, uint8 action, bytes4[] functionSelectors)[] _diamondCut, address _init, bytes _calldata${additionalParamTypes ? ', ' + additionalParamTypes : ''}) external`; } } else { functionName = DiamondHelper.DEFAULT_CUT_FUNCTION; functionSignature = `function ${functionName}(tuple(address facetAddress, uint8 action, bytes4[] functionSelectors)[] _diamondCut, address _init, bytes _calldata) external`; } // console.log('Function signature:', functionSignature); // console.log('Function name:', functionName); // console.log('Additional args:', additionalArgs); // console.log('Formatted cuts:', estimatableCuts); // Use signer instead of provider for the contract const diamond = new ethers_1.Contract(diamondAddress, [functionSignature], signer); // Try calling the function first to get more detailed error try { await diamond.callStatic[functionName](estimatableCuts, ethers_1.ethers.constants.AddressZero, '0x', ...additionalArgs); } catch (error) { if (error instanceof Error) { console.log('Static call error details:', { message: error.message, data: error.data, errorArgs: error.errorArgs, errorName: error.errorName, transaction: error.transaction, code: error.code }); } throw error; } const gasEstimate = await diamond.estimateGas[functionName](estimatableCuts, ethers_1.ethers.constants.AddressZero, '0x', ...additionalArgs); return gasEstimate.mul(DiamondHelper.GAS_BUFFER_PERCENTAGE).div(100); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_1.Logger.error(`Failed to estimate upgrade gas: ${errorMessage}`); throw error; } } async getFunctionSignature(selector, facetAddress) { try { if (facetAddress) { return `Function with selector ${selector} in facet ${facetAddress}`; } return `Function with selector ${selector}`; } catch (error) { return `Unknown Function (${selector})`; } } async validateFacetAddresses(addresses, config) { try { const provider = await (0, hardhatHelpers_1.getProvider)(); const loupeFunctions = this.getLoupeFunctions(config); for (const address of addresses) { // Skip zero address validation if (address === ethers_1.ethers.constants.AddressZero) continue; // Validate address format if (!ethers_1.ethers.utils.isAddress(address)) { logger_1.Logger.error(`Invalid address format: ${address}`); return false; } // Check if address has code const code = await provider.getCode(address); if (code === '0x') { logger_1.Logger.error(`No code at address: ${address}`); return false; } // Check if address is already a facet const diamond = new ethers_1.ethers.Contract(process.env.DIAMOND_ADDRESS, [`function ${loupeFunctions.facets}() view returns (tuple(address facetAddress, bytes4[] functionSelectors)[])`], provider); const facets = await diamond[loupeFunctions.facets](); const isExistingFacet = facets.some((facet) => facet.facetAddress.toLowerCase() === address.toLowerCase()); if (isExistingFacet) { logger_1.Logger.warning(`Address ${address} is already a facet`); // You might want to return false here depending on your requirements } } return true; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_1.Logger.error(`Failed to validate facet addresses: ${errorMessage}`); return false; } } async getUpgradeService(signer, config) { var _a, _b, _c, _d; const diamondAddress = process.env.DIAMOND_ADDRESS; if (!diamondAddress) { throw new Error('Diamond address not set'); } const customFunctionConfig = (_c = (_b = (_a = config.contracts) === null || _a === void 0 ? void 0 : _a.upgradeService) === null || _b === void 0 ? void 0 : _b.customFunctions) === null || _c === void 0 ? void 0 : _c.diamondCut; let functionSignature; if (typeof customFunctionConfig === 'string') { functionSignature = `function ${customFunctionConfig}(tuple(address facetAddress, uint8 action, bytes4[] functionSelectors)[] _diamondCut, address _init, bytes _calldata) external`; } else if (customFunctionConfig && 'name' in customFunctionConfig) { const additionalParamTypes = ((_d = customFunctionConfig.additionalParams) === null || _d === void 0 ? void 0 : _d.map(param => `${param.type}`).join(', ')) || ''; functionSignature = `function ${customFunctionConfig.name}(tuple(address facetAddress, uint8 action, bytes4[] functionSelectors)[] _diamondCut, address _init, bytes _calldata${additionalParamTypes ? ', ' + additionalParamTypes : ''}) external`; } else { functionSignature = 'function diamondCut(tuple(address facetAddress, uint8 action, bytes4[] functionSelectors)[] _diamondCut, address _init, bytes _calldata) external'; } return new ethers_1.ethers.Contract(diamondAddress, [functionSignature], signer); } formatCutsForDiamond(cuts) { return cuts.map(cut => ({ facetAddress: cut.facetAddress, action: cut.action, functionSelectors: cut.functionSelectors })); } } exports.DiamondHelper = DiamondHelper; DiamondHelper.DEFAULT_LOUPE_FUNCTIONS = { facets: 'facets', facetSelectors: 'facetFunctionSelectors', facetAddresses: 'facetAddresses', facetAddress: 'facetAddress' }; DiamondHelper.DEFAULT_CUT_FUNCTION = 'diamondCut'; DiamondHelper.DEFAULT_OWNER_FUNCTION = 'owner'; DiamondHelper.GAS_BUFFER_PERCENTAGE = 120; exports.diamondHelper = DiamondHelper.getInstance(); //# sourceMappingURL=diamondHelpers.js.map