UNPKG

tenderly-wizard-v6

Version:

A tool for managing virtual testnets using Tenderly

366 lines (318 loc) 10.9 kB
import { Terminal, terminal } from "terminal-kit"; import { execSync, ExecSyncOptionsWithStringEncoding } from "child_process"; import VirtualTestNet from "./scripts/virtual-test-net"; import colors from "colors"; import { SingleColumnMenuResponse } from "terminal-kit/Terminal"; import { stripAnsi, updatePackageJson } from "./utils/file-manipulation"; import { findPermissionsFiles, } from "./utils/util"; import path from "path"; import fs from "fs"; async function getTestnetList() { terminal.reset("========================\n"); terminal.black(" 🧙 TENDERLY WIZARD 🧙\n"); terminal.yellow(" Roles v2 / Ethers v6\n"); terminal.black("========================\n"); const vnets = await VirtualTestNet.listVirtualTestnets(); // Get the list of virtual testnets const testnets = vnets.map(vnet => " 🌐 " + vnet.displayName); testnets.unshift("\n"); testnets.unshift(colors.blue("Select an existing Testnet:")); testnets.unshift("\n"); testnets.unshift("========================"); testnets.unshift(colors.green("➕ CREATE TESTNET")); testnets.unshift(colors.green("➕ CREATE TESTNET & SETUP")); testnets.push("\n"); testnets.push("========================"); testnets.push(colors.red("🛑 EXIT")); testnets.push("========================"); // terminal.reset("========================"); let testnet: SingleColumnMenuResponse = await terminal.singleColumnMenu(testnets).promise; console.log( (testnet.selectedText = testnet.selectedText.replace(" 🌐 ", "")) ); if ( testnet.selectedText == colors.red("🛑 EXIT") || testnet.selectedText == "\n" || testnet.selectedText == "========================" || testnet.selectedText == colors.blue("Select Testnet:") ) { terminal.processExit(0); } if (testnet.selectedText == colors.green("➕ CREATE TESTNET")) { const result = await createNewTestnet(terminal); testnet.selectedText = result.testnetName; terminal.processExit(0); } if (testnet.selectedText == colors.green("➕ CREATE TESTNET & SETUP")) { // create new testnet const result = await createNewTestnet(terminal); testnet.selectedText = result.testnetName; // deploy safes const outputSafes = execSync(`npm run deploy:safes`, { stdio: "pipe", }).toString(); console.log(outputSafes); // apply whitelist const outputWhitelist = execSync(`npm run deploy:whitelist`, { stdio: "pipe", }).toString(); console.log(outputWhitelist); // save snapshot const outputSnapshot = execSync(`npm run save:vnet-snapshot`, { stdio: "pipe", }).toString(); console.log(outputSnapshot); terminal.processExit(0); } return testnet; } export async function start() { // update target repo's package.json with scripts if (!process.env.IS_DEV) { updatePackageJson(); } terminal.grabInput(true); terminal.on("key", (name: any) => { if (name === "ESCAPE") { terminal.processExit(0); } }); let testnet: any; testnet = await getTestnetList(); const vnets = await VirtualTestNet.listVirtualTestnets(); const vnet = vnets.find(vnet => vnet.displayName == testnet.selectedText); terminal.reset(); console.log(`VIRTUAL_MAINNET_RPC=${vnet?.admin_rpc}`); console.log(`TENDERLY_TESTNET_UUID=${vnet?.vnet_id}`); console.log(`Select Action for ${testnet.selectedText}:`); const action = await terminal.singleColumnMenu([ "Fork", colors.red("Delete"), "Snapshot", "Activate", "Deploy Safes", "Apply Whitelist", colors.blue("Back"), ]).promise; // fork testnet if (action.selectedIndex == 0) { terminal.reset("Enter the name of the fork name: "); const newTestnet = await terminal.inputField().promise; console.log(`fork testnet: ${newTestnet}`); const result = await VirtualTestNet.forkVirtualTestNet( vnet?.vnet_id as string, newTestnet as string ); console.log(`Forked testnet: ${result.vnet_id}`); } // delete testnet if (action.selectedIndex == 1) { console.log( "Are you sure you want to delete this testnet (Y/N):", testnet.selectedText ); const confirmDelete = await terminal.yesOrNo().promise; if (confirmDelete?.valueOf()) { await VirtualTestNet.deleteVirtualTestNet(vnet?.vnet_id as string); console.log("Deleted testnet"); } //go back to start, goto: await start(); } // save snapshot if (action.selectedIndex == 2) { // @note this function needs to be called from terminal in order to work (needs hardhat to fetch the snapshot) const output = execSync(`npm run save:vnet-snapshot`, { stdio: "pipe", }).toString(); console.log(output); } // activate testnet if (action.selectedIndex == 3) { // get vnet details const testNet = await VirtualTestNet.getTestnet(testnet.selectedText); // // overwrite RPC, Testnet UUID and Fork ID in .env VirtualTestNet.addToEnvFile( "VIRTUAL_MAINNET_RPC", testNet?.admin_rpc ?? "" ); VirtualTestNet.addToEnvFile( "TENDERLY_TESTNET_UUID", testNet?.vnet_id ?? "" ); VirtualTestNet.addToEnvFile( "TENDERLY_FORK_ID", testNet?.network_id?.toString() ?? "1" ); // overwrite Snapshot in .env const output = execSync(`npm run save:vnet-snapshot`, { stdio: "pipe", }).toString(); console.log(output); console.log(`Testnet ${testnet.selectedText} activated successfully`); } // deploy safes if (action.selectedIndex == 4) { // set roles version to v2 await VirtualTestNet.addToEnvFile("ROLES_VERSION", "v2"); // confirmation console.log( "Are you sure you want to deploy default safes to this testnet (Y/N):", testnet.selectedText ); const confirmDeploy = await terminal.yesOrNo().promise; if (confirmDeploy?.valueOf()) { console.log("\nDeploying default safes..."); const output = execSync(`npm run deploy:safes`, { stdio: "pipe", }).toString(); console.log(output); console.log("\nDeployed default safes successfully"); } } // deploy whitelist if (action.selectedIndex == 5) { // set roles version to v2 await VirtualTestNet.addToEnvFile("ROLES_VERSION", "v2"); let output: any; // show menu to select whitelisting options const whitelistOptions = ["Whitelist all", "Whitelist one"]; const whitelistSelection = await terminal.singleColumnMenu(whitelistOptions).promise; // whitelist all if (whitelistSelection.selectedIndex == 0) { console.log(`\nWhitelisting all...`); output = executeWithLogs( `BYPASS_APPROVALS=true npm run deploy:whitelist && npm run save:vnet-snapshot` ); } else if (whitelistSelection.selectedIndex == 1) { // whitelist one console.log(`\nWhitelisting one...`); const permissionsFiles = getWhitelistsV2(); if (permissionsFiles.length === 0) { console.error("No permissions files found."); return; } const rolesDirectory = path.resolve( "../access-control-safes-v2/src/roles" ); const whitelistNames = permissionsFiles.map(file => path.relative(rolesDirectory, file) ); const permissionSelection = await terminal.singleColumnMenu(whitelistNames).promise; const selectedFile = permissionsFiles.find( file => path.relative(rolesDirectory, file) === permissionSelection.selectedText ); if (!selectedFile) { console.error( "Could not find matching permissions file for selection" ); return; } process.env.SELECTED_PERMISSIONS_FILE_V2 = selectedFile; output = executeWithLogs( `npm run execute:whitelist-v2-one && npm run save:vnet-snapshot` ); } if (output) { if (!output?.success) { console.error("Error details:", output?.error); console.error("Error output:", output?.output); } else { console.log(output); console.log("\nApplied whitelist successfully"); } } } if (action.selectedIndex == 6) { goto: await start(); } terminal.processExit(0); } function executeWithLogs(command: string, options = {}) { try { // Merge default options with user provided options const defaultOptions: ExecSyncOptionsWithStringEncoding = { encoding: "utf8", stdio: "pipe", maxBuffer: 1024 * 1024 * 10, // 10MB buffer ...options, }; // Execute the command and capture output const output = execSync(command, defaultOptions); return { success: true, output: stripAnsi(output.toString()), error: null, }; } catch (error: any) { // Capture detailed error information return { success: false, output: error.output ? error.output.toString() : null, error: { message: error.message, status: error.status, signal: error.signal, stderr: error.stderr ? error.stderr.toString() : null, stdout: error.stdout ? error.stdout.toString() : null, command: error.cmd, }, }; } } async function createNewTestnet(terminal: Terminal) { terminal.reset("Enter the name of the new testnet: "); const newTestnet = await terminal.inputField().promise; // select chain // @audit add more chains terminal.yellow("\n\nSelect a Chain: \n"); const chains = ["Ethereum", "Base", "Polygon"]; const chainSelection = await terminal.singleColumnMenu(chains).promise; let chain = 1; if (chainSelection.selectedIndex == 1) { chain = 8453; } else if (chainSelection.selectedIndex == 2) { chain = 137; } // set roles version to v2 await VirtualTestNet.addToEnvFile("ROLES_VERSION", "v2"); console.log(`create testnet: ${newTestnet}`); const result = await VirtualTestNet.createVirtualTestNet( newTestnet as string, chain ); // set env variables await VirtualTestNet.addToEnvFile("TENDERLY_FORK_ID", chain.toString()); // get snapshot const outputSnapshot = execSync(`npm run save:vnet-snapshot`, { stdio: "pipe", }).toString(); console.log(outputSnapshot); return { testnetName: newTestnet as string, result, }; } function getWhitelistsV2() { const rolesDirectory = path.resolve("../access-control-safes-v2/src/roles"); try { // Check if directory exists before trying to read it if (fs.existsSync(rolesDirectory)) { return findPermissionsFiles(rolesDirectory); } else { console.error( `Error: The directory ${rolesDirectory} does not exist. Please make sure the 'access-control-safes-v2' repository is a sibling to this one.` ); return []; } } catch (error) { console.error("Error finding permissions files:", error); return []; } }