UNPKG

@omni-kit/omni-deployer

Version:

Deploy smart contracts across multiple chains using SuperchainFactory

231 lines (230 loc) 12 kB
#!/usr/bin/env node "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const ethers_1 = require("ethers"); const config_1 = require("./config"); const compile_1 = require("./compile"); const artifact_1 = require("./artifact"); const utils_1 = require("./utils"); // ABI definition for interacting with the factory contract const factoryAbi = [ 'function deployContract(uint256[] calldata chainIds, bytes memory bytecode, bytes32 salt) external returns (address)', 'function deployHubAndSpokes(bytes memory hubBytecode, bytes memory spokeBytecode, bytes32 salt, uint256[] calldata spokeChainIds) external returns (address)', 'event ContractDeployed(address indexed contractAddress, uint256 indexed chainId)', 'event CrossChainMessageSent(uint256 indexed chainId, address indexed targetFactory)', ]; /** * Checks if a contract exists at a given address. * @param provider - Ethers provider. * @param address - Address to check. * @returns True if contract code exists at the address, false otherwise. */ function contractExists(provider, address) { return __awaiter(this, void 0, void 0, function* () { const code = yield provider.getCode(address); return code !== '0x'; }); } commander_1.program .command('deploy [configPath]') .alias('d') .description('Deploy a contract across multiple chains using DeploymentFactory') .action((configPath) => __awaiter(void 0, void 0, void 0, function* () { try { // Load deployment configuration const config = yield (0, config_1.loadConfig)(configPath); // Retrieve the private key from environment variables const privateKey = process.env.PRIVATE_KEY; if (!privateKey) { throw new Error('PRIVATE_KEY environment variable is not set. Set it with: export PRIVATE_KEY=0xYourPrivateKey'); } // Compile the contract and load its artifact (0, compile_1.compileContract)(); const artifact = (0, artifact_1.loadArtifact)(config.contractName); // Prepare bytecode and constructor arguments const iface = new ethers_1.ethers.utils.Interface(artifact.abi); const bytecode = artifact.bytecode.object; const encodedArgs = config.constructorArgs && config.constructorArgs.length > 0 ? iface.encodeDeploy(config.constructorArgs) : '0x'; const deployBytecode = ethers_1.ethers.utils.hexConcat([bytecode, encodedArgs]); const formattedSalt = (0, utils_1.formatSalt)(config.salt); // Set up provider, wallet, and factory contract const provider = new ethers_1.ethers.providers.JsonRpcProvider(config.rpcUrl); const wallet = new ethers_1.ethers.Wallet(privateKey, provider); const factory = new ethers_1.Contract(config.factoryContract, factoryAbi, wallet); // Compute CREATE2 address const computedAddress = (0, utils_1.computeCreate2Address)(config.factoryContract, formattedSalt, deployBytecode); // Check if contract already exists at the computed address if (yield contractExists(provider, computedAddress)) { console.log(`Contract already deployed at ${computedAddress}. Use a different salt to deploy a new instance.`); return; } // Estimate gas for the deployment transaction let gasEstimate; try { gasEstimate = yield provider.estimateGas({ from: wallet.address, to: config.factoryContract, data: factory.interface.encodeFunctionData('deployContract', [ config.chains, deployBytecode, formattedSalt, ]), }); console.log('Estimated gas:', gasEstimate.toString()); } catch (error) { console.warn('Gas estimation failed, using manual gas limit of 5,000,000'); gasEstimate = 5000000; // Fallback gas limit } // Deploy the contract const tx = yield factory.deployContract(config.chains, deployBytecode, formattedSalt, { gasLimit: gasEstimate, }); const receipt = yield tx.wait(); // Log transaction details console.log('Transaction hash:', receipt.transactionHash); console.log('Gas used:', receipt.gasUsed.toString()); // Extract deployed contract address from logs const factoryInterface = new ethers_1.ethers.utils.Interface(factoryAbi); const event = receipt.logs.find((log) => { try { const parsed = factoryInterface.parseLog(log); return parsed.name === 'ContractDeployed'; } catch (_a) { return false; } }); if (!event) { throw new Error('FactoryContract not found. Check configuration parameters like Factory Contract address and RPC url'); } const parsedEvent = factoryInterface.parseLog(event); const deployedAddress = ethers_1.ethers.utils.getAddress(parsedEvent.args.contractAddress); console.log('Deployed contract address on local chain:', deployedAddress); // Verify CREATE2 address if (deployedAddress.toLowerCase() !== computedAddress.toLowerCase()) { console.warn('Warning: Local deployed address does not match computed CREATE2 address'); } console.log('Contract deployment initiated across specified chains.'); } catch (error) { console.error('Error:', error.message); process.exit(1); } })); commander_1.program .command('deploy-hs [configPath]') .alias('hs') .description('Deploy hub and spoke contracts across multiple chains using the same address') .action((configPath) => __awaiter(void 0, void 0, void 0, function* () { try { // Load hub-spoke deployment configuration const config = yield (0, config_1.loadHubSpokeConfig)(configPath); // Retrieve the private key from environment variables const privateKey = process.env.PRIVATE_KEY; if (!privateKey) { throw new Error('PRIVATE_KEY environment variable is not set. Set it with: export PRIVATE_KEY=0xYourPrivateKey'); } // Compile the contracts and load artifacts (0, compile_1.compileContract)(); const hubArtifact = (0, artifact_1.loadArtifact)(config.hubContract); const spokeArtifact = (0, artifact_1.loadArtifact)(config.spokeContract); // Prepare hub bytecode and constructor arguments const hubIface = new ethers_1.ethers.utils.Interface(hubArtifact.abi); const hubBytecode = hubArtifact.bytecode.object; const hubEncodedArgs = config.hubConstructorArgs && config.hubConstructorArgs.length > 0 ? hubIface.encodeDeploy(config.hubConstructorArgs) : '0x'; const hubDeployBytecode = ethers_1.ethers.utils.hexConcat([hubBytecode, hubEncodedArgs]); // Prepare spoke bytecode and constructor arguments const spokeIface = new ethers_1.ethers.utils.Interface(spokeArtifact.abi); const spokeBytecode = spokeArtifact.bytecode.object; const spokeEncodedArgs = config.spokeConstructorArgs && config.spokeConstructorArgs.length > 0 ? spokeIface.encodeDeploy(config.spokeConstructorArgs) : '0x'; const spokeDeployBytecode = ethers_1.ethers.utils.hexConcat([spokeBytecode, spokeEncodedArgs]); // Format the salt const formattedSalt = (0, utils_1.formatSalt)(config.salt); // Set up provider, wallet, and factory contract const provider = new ethers_1.ethers.providers.JsonRpcProvider(config.rpcUrl); const wallet = new ethers_1.ethers.Wallet(privateKey, provider); const factory = new ethers_1.Contract(config.factoryContract, factoryAbi, wallet); // Compute CREATE3 address const computedAddress = (0, utils_1.computeCreate3Address)(config.factoryContract, formattedSalt); // Check if contract already exists at the computed address if (yield contractExists(provider, computedAddress)) { console.log(`Contract already deployed at ${computedAddress}. Use a different salt to deploy a new instance.`); return; } // Estimate gas for the hub-spoke deployment let gasEstimate; try { gasEstimate = yield provider.estimateGas({ from: wallet.address, to: config.factoryContract, data: factory.interface.encodeFunctionData('deployHubAndSpokes', [ hubDeployBytecode, spokeDeployBytecode, formattedSalt, config.chains, // Changed from config.spokeChains to config.chains ]), }); console.log('Estimated gas:', gasEstimate.toString()); } catch (error) { console.warn('Gas estimation failed, using manual gas limit of 6,000,000'); gasEstimate = 6000000; // Fallback gas limit for hub-spoke deployment } // Deploy the hub and spoke contracts const tx = yield factory.deployHubAndSpokes(hubDeployBytecode, spokeDeployBytecode, formattedSalt, config.chains, // Changed from config.spokeChains to config.chains { gasLimit: gasEstimate, }); const receipt = yield tx.wait(); // Log transaction details console.log('Transaction hash:', receipt.transactionHash); console.log('Gas used:', receipt.gasUsed.toString()); // Extract deployed hub contract address from logs const factoryInterface = new ethers_1.ethers.utils.Interface(factoryAbi); const event = receipt.logs.find((log) => { try { const parsed = factoryInterface.parseLog(log); return parsed.name === 'ContractDeployed'; } catch (_a) { return false; } }); if (!event) { throw new Error('FactoryContract not found. Check configuration parameters like Factory Contract address and RPC url.'); } const parsedEvent = factoryInterface.parseLog(event); const hubAddress = ethers_1.ethers.utils.getAddress(parsedEvent.args.contractAddress); console.log('Deployed hub contract address on local chain:', hubAddress); // Verify CREATE3 address if (hubAddress.toLowerCase() !== computedAddress.toLowerCase()) { console.warn('Warning: Local deployed address does not match computed CREATE3 address'); } // Log deployment information console.log(`Hub contract (${config.hubContract}) deployed on chain ID ${yield provider.getNetwork().then(n => n.chainId)}`); console.log(`Spoke contract (${config.spokeContract}) deployment initiated on chains:`, config.chains); // Changed from config.spokeChains to config.chains console.log('All contracts will be deployed to the same address:', computedAddress); } catch (error) { console.error('Error:', error.message); process.exit(1); } })); commander_1.program.parse(process.argv);