UNPKG

dop-stick

Version:

Source control tooling for versionable-upgradeable smart contracts

345 lines 15.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DiamondDeployer = void 0; const ethers_1 = require("ethers"); const constructorManager_1 = require("./constructorManager"); const cacheManager_1 = require("./cacheManager"); const logger_1 = require("./logsAndMetrics/core/logger"); const terminal_1 = require("./logsAndMetrics/core/terminal"); const factory_1 = require("./factory-management/typechain/factory"); const provider_1 = require("./provider"); const contractVerifier_1 = require("./contractVerifier"); const deploymentProgress_1 = require("./deploymentProgress"); const timelineLogAdapter_1 = require("./logsAndMetrics/adapters/timelineLogAdapter"); const logFormatters_1 = require("./logsAndMetrics/logFormatters"); const readline = __importStar(require("readline")); const ICONS = { DIAMOND: '💎', ARGS: '📝', DEPLOY: '🚀', SUCCESS: '✓', ERROR: '✖', CONFIRM: '❓' }; const COLORS = { HEADER: '\x1b[95m', SUCCESS: terminal_1.Terminal.colors.success, ERROR: terminal_1.Terminal.colors.error, PENDING: terminal_1.Terminal.colors.muted, RESET: terminal_1.Terminal.colors.reset }; class DiamondDeployer { constructor(config, cache, standardType) { var _a; this.config = config; this.cache = cache; this.standardType = standardType; this.cacheManager = new cacheManager_1.DeploymentCacheManager(config); this.constructorManager = new constructorManager_1.ConstructorManager(standardType, cache); this.factoryManager = factory_1.TypechainFactoryManager.getInstance({ typechainPath: config.paths.typechain || 'typechain-types' }); this.verifier = new contractVerifier_1.ContractVerifier(config); this.progress = new deploymentProgress_1.DeploymentProgress(); this.timeline = new timelineLogAdapter_1.TimelineLogAdapter(); // Initialize cache config this.cacheConfig = { network: process.env.NETWORK || 'Testnet', rpcUrl: process.env.RPC_URL, paths: { cache: (_a = config.paths) === null || _a === void 0 ? void 0 : _a.cache } }; } async deploy() { try { // Start deployment section this.timeline.startSection('💎 DIAMOND DEPLOYMENT'); // 1. Prepare constructor arguments this.timeline.logStep(`${ICONS.ARGS} Preparing constructor arguments`); const constructorArgs = await this.constructorManager.prepareConstructorArgs(); this.timeline.logEmptyStep(); // 2. Get user confirmation const proceed = await this.confirmDeployment(constructorArgs); if (!proceed) { this.timeline.logColoredStep(`${ICONS.ERROR} Deployment cancelled by user`, 0, COLORS.ERROR); throw new Error('Diamond deployment cancelled by user'); } // 3. Deploy diamond this.timeline.logStep(`${ICONS.DEPLOY} Deploying diamond contract`); const diamondAddress = await this.deployDiamond(constructorArgs); // 4. Update cache with diamond address await this.updateCache(diamondAddress); // Log success this.timeline.logEmptyStep(); this.timeline.logColoredStep(`${ICONS.SUCCESS} Diamond deployed at ${logFormatters_1.LogFormatters.makeAddressCopyable(diamondAddress)}`, 0, COLORS.SUCCESS); return diamondAddress; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.timeline.logColoredStep(`${ICONS.ERROR} Deployment failed: ${errorMessage}`, 0, COLORS.ERROR); logger_1.Logger.error(`Diamond deployment failed: ${errorMessage}`); throw error; } } async confirmDeployment(constructorArgs) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); try { // Log constructor arguments in a structured way this.timeline.logStep(`${ICONS.ARGS} Constructor Arguments`); // Format each key-value pair from the Record Object.entries(constructorArgs).forEach(([key, value]) => { let formattedValue = value; if (typeof value === 'string' && ethers_1.ethers.utils.isAddress(value)) { formattedValue = logFormatters_1.LogFormatters.makeAddressCopyable(value); } else if (Array.isArray(value)) { formattedValue = `Array[${value.length}]`; } else if (typeof value === 'object' && value !== null) { formattedValue = 'Object'; } this.timeline.logInnerStep(`• ${key}: ${formattedValue}`, 1); }); this.timeline.logEmptyStep(); this.timeline.logStep(`${ICONS.CONFIRM} Deployment Confirmation`); this.timeline.logInnerColoredStep('• Press [Enter] to proceed or [Ctrl+C] to cancel', 1, COLORS.PENDING); // Create a promise for user input return new Promise((resolve) => { const timeoutDuration = 10000; // 10 seconds let autoConfirmTimeout; // Handle user input rl.question('', (answer) => { clearTimeout(autoConfirmTimeout); this.timeline.logInnerColoredStep('• Confirmed ✓', 1, COLORS.SUCCESS); this.timeline.logEmptyStep(); resolve(true); }); // Auto-confirm timeout autoConfirmTimeout = setTimeout(() => { this.timeline.logInnerColoredStep('• Auto-confirmed (timeout) ✓', 1, COLORS.PENDING); this.timeline.logEmptyStep(); resolve(true); }, timeoutDuration); }); } finally { rl.close(); } } async deployDiamond(args) { var _a, _b, _c, _d; try { const diamondName = (_b = (_a = this.config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.name; console.log("diamondName", diamondName); if (!diamondName) { throw new Error('Diamond contract name not specified in config'); } // Get provider and signer with correct typing const provider = await (0, provider_1.getProviderForCacheConfig)(this.cacheConfig); // Get signer from private key if available const privateKey = process.env.PRIVATE_KEY; const signer = privateKey ? new ethers_1.ethers.Wallet(privateKey, provider) : provider.getSigner(); if (!signer) { throw new Error('No signer available for deployment'); } // Log signer details for debugging logger_1.Logger.debug('Deploying with signer:', { address: await signer.getAddress(), provider: provider.connection.url }); // Get contract factory with signer const factory = await this.factoryManager.getContractFactory(diamondName, signer // Pass signer to factory ); if (!factory) { throw new Error(`Failed to get factory for ${diamondName}`); } // Verify factory has required properties if (!factory.connect || !factory.deploy) { throw new Error('Invalid contract factory - missing required methods'); } // Get deployment arguments const deployArgs = this.prepareDeploymentArguments(args); // Log deployment details logger_1.Logger.debug('Deploying diamond:', { contractName: diamondName, arguments: deployArgs, signerAddress: await signer.getAddress() }); // Deploy with explicit gas settings const diamond = await factory.deploy(...deployArgs, { gasLimit: ethers_1.ethers.utils.hexlify(8000000), gasPrice: await provider.getGasPrice() }); logger_1.Logger.debug('Deployment transaction sent:', { hash: diamond.deployTransaction.hash, from: diamond.deployTransaction.from, nonce: diamond.deployTransaction.nonce }); // Wait for deployment await diamond.deployed(); // Verify deployment const code = await provider.getCode(diamond.address); if (code === '0x' || code === '0x0') { throw new Error('Deployment failed - no code at contract address'); } logger_1.Logger.debug('Diamond deployed successfully:', { address: diamond.address, transactionHash: diamond.deployTransaction.hash, bytecodeLength: code.length }); return diamond.address; } catch (err) { const errorDetails = { contractName: (_d = (_c = this.config.contracts) === null || _c === void 0 ? void 0 : _c.diamond) === null || _d === void 0 ? void 0 : _d.name, args, standardType: this.standardType }; if (err instanceof Error) { errorDetails.name = err.name; errorDetails.message = err.message; errorDetails.stack = err.stack; // Handle ethers specific errors if ('code' in err) { errorDetails.code = err.code; } if ('reason' in err) { errorDetails.reason = err.reason; } if ('transaction' in err) { errorDetails.transaction = err.transaction; } if ('receipt' in err) { errorDetails.receipt = err.receipt; } } else { errorDetails.error = err; errorDetails.message = 'Unknown error occurred'; } logger_1.Logger.error('Diamond deployment failed:', errorDetails); // Re-throw with better error message throw new Error(`Diamond deployment failed: ${err instanceof Error ? err.message : 'Unknown error occurred'}`); } } prepareDeploymentArguments(args) { var _a, _b; // Add detailed logging logger_1.Logger.debug('Preparing deployment arguments:', { args }); const owner = process.env.DIAMOND_OWNER || process.env.DEPLOYER_ADDRESS; if (!owner) { throw new Error('DIAMOND_OWNER or DEPLOYER_ADDRESS must be set in environment'); } // Validate owner address if (!ethers_1.ethers.utils.isAddress(owner)) { throw new Error(`Invalid owner address: ${owner}`); } switch (this.standardType) { case 'standard-type-1': { const init = (_a = this.cache.coreFacets['core/init']) === null || _a === void 0 ? void 0 : _a.address; if (!init) { throw new Error('Init facet address not found in cache'); } // Validate init address if (!ethers_1.ethers.utils.isAddress(init)) { throw new Error(`Invalid init address: ${init}`); } const deployArgs = [ args._diamondCut || [], { owner, init, initCalldata: args.initCalldata || '0x' } ]; // Log prepared arguments logger_1.Logger.debug('Prepared standard-type-1 arguments:', deployArgs); return deployArgs; } case 'standard-type-2': return [ args._diamondCut || [], { owner } ]; case 'standard-type-3': { const cutFacet = (_b = this.cache.coreFacets['core/cut']) === null || _b === void 0 ? void 0 : _b.address; if (!cutFacet) { throw new Error('Diamond Cut facet address not found in cache'); } return [owner, cutFacet]; } default: throw new Error(`Unsupported diamond standard type: ${this.standardType}`); } } verifyBytecode(bytecode) { if (!bytecode || bytecode === '0x' || bytecode === '0x0') { throw new Error('Contract bytecode is empty or invalid'); } // Check minimum bytecode length (at least some basic initialization code) if (bytecode.length < 100) { throw new Error('Contract bytecode seems too short'); } } async updateCache(diamondAddress) { this.cache.diamondAddress = diamondAddress; await this.cacheManager.save(this.cache); } verifyDeploymentAddresses(args) { const verifyAddress = (value, path) => { if (typeof value === 'string' && value.startsWith('0x')) { if (!ethers_1.ethers.utils.isAddress(value)) { throw new Error(`Invalid address at ${path}: ${value}`); } if (value === ethers_1.ethers.constants.AddressZero) { throw new Error(`Zero address not allowed at ${path}`); } } }; const recursiveVerify = (obj, path) => { if (Array.isArray(obj)) { obj.forEach((item, index) => recursiveVerify(item, `${path}[${index}]`)); } else if (typeof obj === 'object' && obj !== null) { Object.entries(obj).forEach(([key, value]) => { recursiveVerify(value, path ? `${path}.${key}` : key); }); } else { verifyAddress(obj, path); } }; recursiveVerify(args, ''); } } exports.DiamondDeployer = DiamondDeployer; //# sourceMappingURL=diamondDeployer.js.map