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