dop-stick
Version:
Source control tooling for versionable-upgradeable smart contracts
333 lines • 18.3 kB
JavaScript
;
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