UNPKG

@holographxyz/cli

Version:
308 lines (307 loc) 17.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const inquirer = tslib_1.__importStar(require("inquirer")); const fs = tslib_1.__importStar(require("fs-extra")); const core_1 = require("@oclif/core"); const networks_1 = require("@holographxyz/networks"); const bytecodes_1 = require("../../utils/bytecodes"); const config_1 = require("../../utils/config"); const utils_1 = require("../../utils/utils"); const network_monitor_1 = require("../../utils/network-monitor"); const contract_deployment_1 = require("../../utils/contract-deployment"); const signature_1 = require("../../utils/signature"); const holograph_contract_events_1 = require("../../utils/holograph-contract-events"); const validation_1 = require("../../utils/validation"); async function getCodeFromFile(prompt) { const codeFile = await (0, validation_1.checkStringFlag)(undefined, prompt); if (await fs.pathExists(codeFile)) { return (0, validation_1.validateBytes)(await fs.readFile(codeFile, 'utf8')); } throw new Error('The file "' + codeFile + '" does not exist.'); } class Contract extends core_1.Command { static hidden = false; static description = 'Deploy a Holographable contract.'; static examples = [ '$ <%= config.bin %> <%= command.id %> --deploymentType="deployedTx" --tx="0xdb8b393dd18a71b386c8de75b87310c0c8ded0c57cf6b4c5bab52873d54d1e8a" --txNetwork="goerli"', ]; static flags = { ...contract_deployment_1.deploymentFlags, }; /** * Contract class variables */ networkMonitor; // eslint-disable-next-line complexity async run() { this.log('Loading user configurations...'); const { userWallet, configFile, supportedNetworksOptions } = await (0, config_1.ensureConfigFileIsValid)(this.config.configDir, undefined, true); const { flags } = await this.parse(Contract); this.log('User configurations loaded.'); let configHash; let tx; let txNetwork; let deploymentConfig = { config: { contractType: '', chainType: '', salt: '', byteCode: '', initCode: '', }, signature: { r: '', s: '', v: 0, }, signer: userWallet.address, }; let deploymentConfigFile; this.networkMonitor = new network_monitor_1.NetworkMonitor({ parent: this, configFile, debug: this.debug, userWallet, verbose: false, }); core_1.CliUx.ux.action.start('Loading network RPC providers'); await this.networkMonitor.run(true); core_1.CliUx.ux.action.stop(); let chainType; let chainId; let salt; let bytecodeType; const contractTypes = ['HolographERC20', 'HolographERC721']; let contractType; let contractTypeHash; let byteCode; let eventConfig = (0, holograph_contract_events_1.allEventsEnabled)(); let sourceInitCode = (0, utils_1.generateInitCode)(['bytes'], ['0x00']); let initCode = (0, utils_1.generateInitCode)(['bytes'], [sourceInitCode]); let tokenName; let tokenSymbol; let domainSeperator; const domainVersion = '1'; let decimals; let collectionName; let collectionSymbol; let royaltyBps; let configHashBytes; let sig; let signature; let needToSign = false; const deploymentType = await (0, validation_1.checkDeploymentTypeFlag)(flags.deploymentType, 'Select the type of deployment to use'); switch (deploymentType) { case contract_deployment_1.DeploymentType.deployedTx: txNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.txNetwork, 'Select the network on which the transaction was executed'); tx = await (0, validation_1.checkTransactionHashFlag)(flags.tx, 'Enter the hash of transaction that deployed the original contract'); break; case contract_deployment_1.DeploymentType.deploymentConfig: deploymentConfigFile = await (0, validation_1.checkStringFlag)(flags.deploymentConfig, 'Enter the config file to use'); if (await fs.pathExists(deploymentConfigFile)) { deploymentConfig = (await fs.readJson(deploymentConfigFile)); } else { throw new Error('The file "' + deploymentConfigFile + '" does not exist.'); } break; case contract_deployment_1.DeploymentType.createConfig: chainType = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, undefined, 'Select the primary network of the contract (does not prepend chainId to tokenIds)'); chainId = '0x' + networks_1.networks[chainType].holographId.toString(16).padStart(8, '0'); deploymentConfig.config.chainType = chainId; salt = '0x' + (0, utils_1.remove0x)(await (0, validation_1.checkTokenIdFlag)(undefined, 'Enter a bytes32 hash or number to use for salt hash')).padStart(64, '0'); deploymentConfig.config.salt = salt; bytecodeType = await (0, validation_1.checkBytecodeTypeFlag)(undefined, 'Select the bytecode type to deploy'); contractType = bytecodeType === bytecodes_1.BytecodeType.Custom ? await (0, validation_1.checkOptionFlag)(contractTypes, undefined, 'Select the contract type to create') : bytecodeType === bytecodes_1.BytecodeType.SampleERC20 ? 'HolographERC20' : 'HolographERC721'; contractTypeHash = '0x' + utils_1.web3.utils.asciiToHex(contractType).slice(2).padStart(64, '0'); deploymentConfig.config.contractType = contractTypeHash; byteCode = bytecodeType === bytecodes_1.BytecodeType.Custom ? await getCodeFromFile('Provide the filename containing the hex encoded string of the bytecode you want to use') : bytecodes_1.bytecodes[bytecodeType]; switch (contractType) { case 'HolographERC20': tokenName = await (0, validation_1.checkStringFlag)(undefined, 'Enter the token name to use'); tokenSymbol = await (0, validation_1.checkStringFlag)(undefined, 'Enter the token symbol to use'); domainSeperator = tokenName; decimals = await (0, validation_1.checkNumberFlag)(undefined, 'Enter the number of decimals [0-18] to use. The recommended number is 18.'); if (decimals > 18 || decimals < 0) { throw new Error('Invalid decimals was provided: ' + decimals.toString()); } switch (bytecodeType) { case bytecodes_1.BytecodeType.SampleERC20: eventConfig = (0, holograph_contract_events_1.configureEvents)([1, 2]); // [HolographERC20Event.bridgeIn, HolographERC20Event.bridgeOut] sourceInitCode = (0, utils_1.generateInitCode)(['address'], [userWallet.address]); break; case bytecodes_1.BytecodeType.Custom: eventConfig = (0, holograph_contract_events_1.configureEvents)((await inquirer.prompt([ { type: 'checkbox', message: 'Select the events to enable', name: 'erc20events', choices: holograph_contract_events_1.HolographERC20Event, }, ])).erc20events); sourceInitCode = await getCodeFromFile('Provide the filename containing the hex encoded string of the initCode you want to use'); break; } initCode = (0, utils_1.generateInitCode)(['string', 'string', 'uint8', 'uint256', 'string', 'string', 'bool', 'bytes'], [ tokenName, tokenSymbol, decimals, eventConfig, domainSeperator, domainVersion, false, sourceInitCode, ]); break; case 'HolographERC721': collectionName = await (0, validation_1.checkStringFlag)(undefined, 'Enter the collection name to use'); collectionSymbol = await (0, validation_1.checkStringFlag)(undefined, 'Enter the collection symbol to use'); royaltyBps = await (0, validation_1.checkNumberFlag)(undefined, 'Enter the percentage of royalty to collect in basepoints. (1 = 0.01%, 10000 = 100%)'); if (royaltyBps > 10000 || royaltyBps < 0) { throw new Error('Invalid royalty basepoints was provided: ' + royaltyBps.toString()); } switch (bytecodeType) { case bytecodes_1.BytecodeType.CxipERC721: eventConfig = (0, holograph_contract_events_1.configureEvents)([1, 2, 7]); // [HolographERC721Event.bridgeIn, HolographERC721Event.bridgeOut, HolographERC721Event.afterBurn] sourceInitCode = (0, utils_1.generateInitCode)(['bytes32', 'address', 'bytes'], [ '0x' + utils_1.web3.utils.asciiToHex('CxipERC721').slice(2).padStart(64, '0'), await this.networkMonitor.registryContract.address, (0, utils_1.generateInitCode)(['address'], [userWallet.address]), ]); break; case bytecodes_1.BytecodeType.SampleERC721: eventConfig = (0, holograph_contract_events_1.configureEvents)([1, 2, 7]); // [HolographERC721Event.bridgeIn, HolographERC721Event.bridgeOut, HolographERC721Event.afterBurn] sourceInitCode = (0, utils_1.generateInitCode)(['address'], [userWallet.address]); break; case bytecodes_1.BytecodeType.Custom: eventConfig = (0, holograph_contract_events_1.configureEvents)((await inquirer.prompt([ { type: 'checkbox', message: 'Select the events to enable', name: 'erc721events', choices: holograph_contract_events_1.HolographERC721Event, }, ])).erc721events); sourceInitCode = await getCodeFromFile('Provide the filename containing the hex encoded string of the initCode you want to use'); break; } initCode = (0, utils_1.generateInitCode)(['string', 'string', 'uint16', 'uint256', 'bool', 'bytes'], [ collectionName, collectionSymbol, royaltyBps, eventConfig, false, sourceInitCode, ]); break; } deploymentConfig.config.byteCode = byteCode; deploymentConfig.config.initCode = initCode; configHash = (0, utils_1.sha3)('0x' + deploymentConfig.config.contractType.slice(2) + deploymentConfig.config.chainType.slice(2) + deploymentConfig.config.salt.slice(2) + (0, utils_1.sha3)(deploymentConfig.config.byteCode).slice(2) + (0, utils_1.sha3)(deploymentConfig.config.initCode).slice(2) + deploymentConfig.signer.slice(2)); configHashBytes = utils_1.web3.utils.hexToBytes(configHash); needToSign = true; break; } const targetNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.targetNetwork, 'Select the network on which the contract will be executed', txNetwork); if (needToSign) { sig = await this.networkMonitor.wallets[targetNetwork].signMessage(configHashBytes); signature = (0, signature_1.strictECDSA)({ r: '0x' + sig.slice(2, 66), s: '0x' + sig.slice(66, 130), v: '0x' + sig.slice(130, 132), }); deploymentConfig.signature.r = signature.r; deploymentConfig.signature.s = signature.s; deploymentConfig.signature.v = Number.parseInt(signature.v, 16); } if (deploymentType === contract_deployment_1.DeploymentType.deployedTx) { core_1.CliUx.ux.action.start('Retrieving transaction details from "' + txNetwork + '" network'); const deploymentTransaction = await this.networkMonitor.providers[txNetwork].getTransaction(tx); deploymentConfig = (0, contract_deployment_1.decodeDeploymentConfigInput)(deploymentTransaction.data); core_1.CliUx.ux.action.stop(); } configHash = (0, utils_1.sha3)('0x' + deploymentConfig.config.contractType.slice(2) + deploymentConfig.config.chainType.slice(2) + deploymentConfig.config.salt.slice(2) + (0, utils_1.sha3)(deploymentConfig.config.byteCode).slice(2) + (0, utils_1.sha3)(deploymentConfig.config.initCode).slice(2) + deploymentConfig.signer.slice(2)); if (deploymentType !== contract_deployment_1.DeploymentType.deploymentConfig) { const configFilePrompt = await inquirer.prompt([ { name: 'shouldSave', message: 'Would you like to export/save the deployment config file?', type: 'confirm', default: true, }, ]); if (configFilePrompt.shouldSave) { deploymentConfigFile = await (0, validation_1.checkStringFlag)(undefined, 'Enter the path and file where to save (ie ./deploymentConfig.json)'); await fs.ensureFile(deploymentConfigFile); await fs.writeFile(deploymentConfigFile, JSON.stringify(deploymentConfig, undefined, 2), 'utf8'); this.log('File successfully saved to "' + deploymentConfigFile + '"'); } } core_1.CliUx.ux.action.start('Checking that contract is not already deployed on "' + targetNetwork + '" network'); const contractAddress = await this.networkMonitor.registryContract .connect(this.networkMonitor.providers[targetNetwork]) .getContractTypeAddress(configHash); core_1.CliUx.ux.action.stop(); if (contractAddress !== utils_1.zeroAddress) { throw new Error('Contract already deployed at ' + contractAddress + ' on "' + targetNetwork + '" network'); } const blockchainPrompt = await inquirer.prompt([ { name: 'shouldContinue', message: 'Next steps submit the transaction, would you like to proceed?', type: 'confirm', default: true, }, ]); if (!blockchainPrompt.shouldContinue) { throw new Error('Dropping command, no blockchain transactions executed'); } core_1.CliUx.ux.action.start('Deploying contract'); const receipt = await this.networkMonitor.executeTransaction({ network: targetNetwork, contract: this.networkMonitor.factoryContract.connect(this.networkMonitor.providers[targetNetwork]), methodName: 'deployHolographableContract', args: [deploymentConfig.config, deploymentConfig.signature, deploymentConfig.signer], waitForReceipt: true, }); core_1.CliUx.ux.action.stop(); if (receipt === null) { throw new Error('failed to confirm that the transaction was mined'); } else { const logs = this.networkMonitor.decodeBridgeableContractDeployedEvent(receipt, this.networkMonitor.factoryAddress); if (logs === undefined) { throw new Error('failed to extract transfer event from transaction receipt'); } else { const deploymentAddress = logs[0]; this.log(`Contract has been deployed to address ${deploymentAddress} on ${targetNetwork} network`); this.exit(); } } } } exports.default = Contract;