UNPKG

@simbachain/hardhat

Version:
422 lines (393 loc) 18.8 kB
import { SimbaConfig, chooseApplicationFromList, getBlockchains, getStorages, primaryConstructorRequiresArgs, primaryConstructorInputs, authErrors, } from '@simbachain/web3-suites'; // const log: Logger = new Logger({minLevel: "error"}); import {default as prompt} from 'prompts'; import {default as chalk} from 'chalk'; import axios from "axios"; interface DeploymentArguments { [key: string]: any; } interface DeploymentRequest { blockchain: string; app_name: string; args: DeploymentArguments; storage?: string; api_name?: string; display_name?: string; language?: string; code?: string; pre_txn_hook?: string; lib_name?: string; } /** * deploy a contract to your organisation and application on blocks * @param primary name of primary contract if user wants to skip the prompt to choose contract * @param deployInfo used to non-interactively deploy contract * @returns void | AxiosError */ export const deployContract = async ( primary?: string, deployInfo?: Record<any, any>, ) => { SimbaConfig.log.debug(`:: ENTER :`); const config = new SimbaConfig(); if (!config.ProjectConfigStore.has("contracts_info")) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : Please export your contract first with "npx hardhat simba export".`)}`); return; } const blockchainList = await getBlockchains(config); const storageList = await getStorages(config); if (!config.application) { try { await chooseApplicationFromList(config); } catch (e) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : ${JSON.stringify(e)}`)}`); return; } } const contractsInfo = SimbaConfig.ProjectConfigStore.get("contracts_info"); if (!contractsInfo || !Object.keys(contractsInfo).length) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: no contracts present in your contracts_info in simba.json. Did you forget to export contracts first by running ${chalk.greenBright(`$ npx hardhat simba export`)} ?`)}`); return; } let contractName; if (!deployInfo) { if (primary) { if ((primary as string) in contractsInfo) { SimbaConfig.ProjectConfigStore.set('primary', primary); contractName = primary; } else { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : Primary contract ${primary} is not the name of a contract in this project`)}`); return; } } else { const choices = []; for (const [contractName, _] of Object.entries(contractsInfo)) { choices.push({title: contractName, value: contractName}); } const response = await prompt({ type: 'select', name: 'contract_name', message: 'Please pick which contract you want to deploy', choices, }); if (!response.contract_name) { SimbaConfig.log.error(`${chalk.redBright('\nsimba: EXIT : No contract selected for deployment!')}`); throw new Error('No Contract Selected!'); } contractName = response.contract_name; SimbaConfig.ProjectConfigStore.set("primary", contractName); } } let chosen: any = {}; let deployArgs: DeploymentArguments = {}; let id; let _isLibrary: boolean = false; let sourceCode; if (!deployInfo) { const contractInfo = contractsInfo[contractName]; sourceCode = contractInfo.source_code; const contractType = contractInfo.contract_type; _isLibrary = (contractType === "library") ? true : false; SimbaConfig.log.info(`${chalk.cyanBright(`\nsimba deploy: gathering info for deployment of contract ${chalk.greenBright(`${contractName}`)}`)}`) const questions: prompt.PromptObject[] = [ { type: 'text', name: 'api', message: `Please enter an API name for contract ${chalk.greenBright(`${contractName}`)} [^[w-]*$]`, validate: (str: string): boolean => !!/^[\w-]*$/.exec(str), }, { type: 'select', name: 'blockchain', message: 'Please choose the blockchain to deploy to.', choices: blockchainList, initial: 0, }, { type: 'select', name: 'storage', message: 'Please choose the storage to use.', choices: storageList, initial: 0, }, ]; const constructorRequiresParams = await primaryConstructorRequiresArgs(); const paramInputQuestions: any = []; let inputNameToTypeMap: any = {}; let inputsAsJson = true; if (constructorRequiresParams) { const constructorInputs = await primaryConstructorInputs(); const allParamsByJson = "enter all params as json object"; const paramsOneByOne = "enter params one by one from prompts"; const paramInputChoices = [paramsOneByOne, allParamsByJson]; const paramChoices = []; for (let i = 0; i < paramInputChoices.length; i++) { const entry = paramInputChoices[i]; paramChoices.push({ title: entry, value: entry, }); } const promptChosen = await prompt({ type: 'select', name: 'input_method', message: 'Your constructor parameters can be input as either a single json object or one by one from prompts. Which would you prefer?', choices: paramChoices, }); if (!promptChosen.input_method) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : no param input method chosen!`)}`) return; } if (promptChosen.input_method === allParamsByJson) { questions.push({ type: 'text', name: 'args', message: 'Please enter any arguments for the contract as a JSON dictionary.', validate: (contractArgs: string): boolean => { if (!contractArgs) { return true; } // Allow empty strings try { JSON.parse(contractArgs); return true; } catch { return false; } }, }); } else { inputsAsJson = false; for (let i = 0; i < constructorInputs.length; i++) { const inputEntry = constructorInputs[i]; const paramType = inputEntry.type; const paramName = inputEntry.name; inputNameToTypeMap[paramName] = paramType; paramInputQuestions.push({ type: "text", name: paramName, message: `please input value for param ${chalk.greenBright(`${paramName}`)} of type ${chalk.greenBright(`${paramType}`)}`, }); } } } chosen = await prompt(questions); let inputsChosen = {} as any; if (!inputsAsJson) { inputsChosen = await prompt(paramInputQuestions); SimbaConfig.log.debug(`:: inputsChosen : ${JSON.stringify(inputsChosen)}`); for (const key in inputsChosen) { if (!inputNameToTypeMap[key].startsWith("string") || !inputNameToTypeMap[key].startsWith("address")) { try { // trying and catching. there are custom data types that users can define // that we won't be able to anticipate. so we try to parse those, // and if they're really just extensions of 'string', then we continue inputsChosen[key] = JSON.parse(inputsChosen[key]); } catch (e) { continue; } } } } if (!chosen.api) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : No API Name chosen!`)}`); return; } if (!chosen.blockchain) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : No blockchain chosen!`)}`); return; } if (!chosen.storage) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : No storage chosen!`)}`) return; } if (constructorRequiresParams && !chosen.args && !inputsChosen) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : Your contract requires constructor arguments`)}`) return; } id = contractInfo.design_id; if (chosen.args) { deployArgs = JSON.parse(chosen.args) as DeploymentArguments; } else { if (config.ProjectConfigStore.has('defaultArgs')) { deployArgs = config.ProjectConfigStore.get('defaultArgs') as DeploymentArguments; } else { if (inputsChosen) { deployArgs = JSON.parse(JSON.stringify(inputsChosen)); } } } } let deployURL; let deployment: DeploymentRequest; if (deployInfo) { contractName = deployInfo.api; deployURL = deployInfo.url; deployment = { blockchain: deployInfo.blockchain, storage: deployInfo.storage, api_name: deployInfo.api, app_name: config.application.name, display_name: config.application.name, args: deployInfo.args, } } else { if (_isLibrary) { deployURL = `v2/organisations/${config.organisation.id}/deployed_artifacts/create/`; const b64CodeBuffer = Buffer.from(sourceCode) const base64CodeString = b64CodeBuffer.toString('base64') deployment = { args: deployArgs, language: "Solidity", code: base64CodeString, blockchain: chosen.blockchain, app_name: config.application.name, lib_name: config.ProjectConfigStore.get("primary"), }; } else { deployURL = `v2/organisations/${config.organisation.id}/contract_designs/${id}/deploy/`; deployment = { blockchain: chosen.blockchain, storage: chosen.storage, api_name: chosen.api, app_name: config.application.name, display_name: config.application.name, args: deployArgs, }; } } const authStore = await SimbaConfig.authStore(); if (!authStore) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: no authStore created. Please make sure your baseURL is properly configured in your simba.json`)}`); return Promise.reject(new Error(authErrors.badAuthProviderInfo)); } try { const resp = await authStore.doPostRequest( deployURL, deployment, "application/json", true, ); SimbaConfig.log.debug(`:: resp : ${JSON.stringify(resp)}`); if (!resp) { SimbaConfig.log.error(`${chalk.redBright(`simba: EXIT : error deploying contract`)}`); return; } const deployment_id = resp.deployment_id; const transaction_hash = resp.transaction_hash; config.ProjectConfigStore.set('deployment_id', deployment_id); SimbaConfig.log.info(`${chalk.cyanBright(`\nsimba deploy: Contract deployment ID for contract ${contractName}:`)} ${chalk.greenBright(`${deployment_id}`)}`); SimbaConfig.log.info(`${chalk.cyanBright(`\nsimba deploy: txn hash for contract ${contractName}:`)} ${chalk.greenBright(`${transaction_hash}`)}`); let deployed = false; let lastState = null; let retVal = null; do { const checkDeployURL = `v2/organisations/${config.organisation.id}/deployments/${deployment_id}/`; const check_resp = await authStore.doGetRequest( checkDeployURL, ) as Record<any, any>; if (!check_resp) { SimbaConfig.log.error(`${chalk.redBright(`simba: EXIT : error checking deployment URL`)}`); return; } const state: any = check_resp.state; SimbaConfig.log.debug(`:: state : ${state}`); switch (state) { case 'INITIALISED': if (lastState !== state) { lastState = state; SimbaConfig.log.info( `${chalk.cyanBright('\nsimba deploy: Your contract deployment has been initialised...')}`, ); } break; case 'EXECUTING': if (lastState !== state) { lastState = state; SimbaConfig.log.info(`${chalk.cyanBright('\nsimba deploy: deployment is executing...')}`); } break; case 'COMPLETED': deployed = true; const contractName = config.ProjectConfigStore.get("primary"); const contractsInfo = config.ProjectConfigStore.get("contracts_info") ? config.ProjectConfigStore.get("contracts_info") : {}; contractsInfo[contractName] = contractsInfo[contractName] ? contractsInfo[contractName] : {}; contractsInfo[contractName].application = SimbaConfig.application.name; if (!_isLibrary) { const contractAddress = check_resp.primary.address; contractsInfo[contractName].address = contractAddress; contractsInfo[contractName].deployment_id = deployment_id; contractsInfo[contractName].transaction_hash = transaction_hash; config.ProjectConfigStore.set("contracts_info", contractsInfo); const most_recent_deployment_info = { address: contractAddress, transaction_hash, deployment_id, type: "contract" }; config.ProjectConfigStore.set('most_recent_deployment_info', most_recent_deployment_info); SimbaConfig.log.info( `${chalk.cyanBright(`\nsimba deploy: contract ${chalk.greenBright(`${contractName}`)} was deployed to ${chalk.greenBright(`${contractAddress}`)} with deployment_id ${chalk.greenBright(`${deployment_id}`)} and transaction_hash ${chalk.greenBright(`${transaction_hash}`)}. Information pertaining to this deployment can be found in your simba.json under contracts_info.${contractName}.`)}`, ); } else { const deploymentInfo = check_resp.deployment; for (let i = 0; i < deploymentInfo.length; i++) { const entry = deploymentInfo[i]; if (!(entry.name === contractName)) { continue; } const libraryAddress = entry.address; let contractsInfo = config.ProjectConfigStore.get("contracts_info") as any; contractsInfo[contractName].address = libraryAddress; contractsInfo[contractName].deployment_id = deployment_id; contractsInfo[contractName].transaction_hash = transaction_hash; config.ProjectConfigStore.set("contracts_info", contractsInfo); const most_recent_deployment_info = { address: libraryAddress, deployment_id, transaction_hash, type: "library", }; const libraryAddresses = config.ProjectConfigStore.get("library_addresses") ? config.ProjectConfigStore.get("library_addresses") : {}; libraryAddress[contractName] = libraryAddress; config.ProjectConfigStore.set("library_addresses", libraryAddresses); config.ProjectConfigStore.set("most_recent_deployment_info", most_recent_deployment_info); SimbaConfig.log.info(`${chalk.cyanBright(`simba: your library was deployed to address ${chalk.greenBright(`${libraryAddress}`)}, with deployment_id ${chalk.greenBright(`${deployment_id}`)} and transaction_hash ${chalk.greenBright(`${transaction_hash}`)}. Information pertaining to this deployment can be found in your simba.json`)}`); } } break; case 'ABORTED': deployed = true; SimbaConfig.log.error(`${chalk.red('\nsimba deploy: Your contract deployment was aborted...')}`); SimbaConfig.log.error(`${chalk.red(`\nsimba deploy: EXIT : ${check_resp.error}`)}${check_resp.error}`); SimbaConfig.log.debug(`:: EXIT :`); retVal = new Error(check_resp.error); break; } } while (!deployed); Promise.resolve(retVal); } catch (error) { if (axios.isAxiosError(error) && error.response) { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : ${JSON.stringify(error.response.data)}`)}`); return error; } else { SimbaConfig.log.error(`${chalk.redBright(`\nsimba: EXIT : ${JSON.stringify(error)}`)}`); return error; } } SimbaConfig.log.debug(`:: EXIT :`); return; }