tenderly-wizard-v6
Version:
A tool for managing virtual testnets using Tenderly
285 lines (284 loc) • 12.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
const terminal_kit_1 = require("terminal-kit");
const child_process_1 = require("child_process");
const virtual_test_net_1 = __importDefault(require("./scripts/virtual-test-net"));
const colors_1 = __importDefault(require("colors"));
const file_manipulation_1 = require("./utils/file-manipulation");
const util_1 = require("./utils/util");
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
async function getTestnetList() {
terminal_kit_1.terminal.reset("========================\n");
terminal_kit_1.terminal.black(" 🧙 TENDERLY WIZARD 🧙\n");
terminal_kit_1.terminal.yellow(" Roles v2 / Ethers v6\n");
terminal_kit_1.terminal.black("========================\n");
const vnets = await virtual_test_net_1.default.listVirtualTestnets(); // Get the list of virtual testnets
const testnets = vnets.map(vnet => " 🌐 " + vnet.displayName);
testnets.unshift("\n");
testnets.unshift(colors_1.default.blue("Select an existing Testnet:"));
testnets.unshift("\n");
testnets.unshift("========================");
testnets.unshift(colors_1.default.green("➕ CREATE TESTNET"));
testnets.unshift(colors_1.default.green("➕ CREATE TESTNET & SETUP"));
testnets.push("\n");
testnets.push("========================");
testnets.push(colors_1.default.red("🛑 EXIT"));
testnets.push("========================");
// terminal.reset("========================");
let testnet = await terminal_kit_1.terminal.singleColumnMenu(testnets).promise;
console.log((testnet.selectedText = testnet.selectedText.replace(" 🌐 ", "")));
if (testnet.selectedText == colors_1.default.red("🛑 EXIT") ||
testnet.selectedText == "\n" ||
testnet.selectedText == "========================" ||
testnet.selectedText == colors_1.default.blue("Select Testnet:")) {
terminal_kit_1.terminal.processExit(0);
}
if (testnet.selectedText == colors_1.default.green("➕ CREATE TESTNET")) {
const result = await createNewTestnet(terminal_kit_1.terminal);
testnet.selectedText = result.testnetName;
terminal_kit_1.terminal.processExit(0);
}
if (testnet.selectedText == colors_1.default.green("➕ CREATE TESTNET & SETUP")) {
// create new testnet
const result = await createNewTestnet(terminal_kit_1.terminal);
testnet.selectedText = result.testnetName;
// deploy safes
const outputSafes = (0, child_process_1.execSync)(`npm run deploy:safes`, {
stdio: "pipe",
}).toString();
console.log(outputSafes);
// apply whitelist
const outputWhitelist = (0, child_process_1.execSync)(`npm run deploy:whitelist`, {
stdio: "pipe",
}).toString();
console.log(outputWhitelist);
// save snapshot
const outputSnapshot = (0, child_process_1.execSync)(`npm run save:vnet-snapshot`, {
stdio: "pipe",
}).toString();
console.log(outputSnapshot);
terminal_kit_1.terminal.processExit(0);
}
return testnet;
}
async function start() {
// update target repo's package.json with scripts
if (!process.env.IS_DEV) {
(0, file_manipulation_1.updatePackageJson)();
}
terminal_kit_1.terminal.grabInput(true);
terminal_kit_1.terminal.on("key", (name) => {
if (name === "ESCAPE") {
terminal_kit_1.terminal.processExit(0);
}
});
let testnet;
testnet = await getTestnetList();
const vnets = await virtual_test_net_1.default.listVirtualTestnets();
const vnet = vnets.find(vnet => vnet.displayName == testnet.selectedText);
terminal_kit_1.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_kit_1.terminal.singleColumnMenu([
"Fork",
colors_1.default.red("Delete"),
"Snapshot",
"Activate",
"Deploy Safes",
"Apply Whitelist",
colors_1.default.blue("Back"),
]).promise;
// fork testnet
if (action.selectedIndex == 0) {
terminal_kit_1.terminal.reset("Enter the name of the fork name: ");
const newTestnet = await terminal_kit_1.terminal.inputField().promise;
console.log(`fork testnet: ${newTestnet}`);
const result = await virtual_test_net_1.default.forkVirtualTestNet(vnet?.vnet_id, newTestnet);
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_kit_1.terminal.yesOrNo().promise;
if (confirmDelete?.valueOf()) {
await virtual_test_net_1.default.deleteVirtualTestNet(vnet?.vnet_id);
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 = (0, child_process_1.execSync)(`npm run save:vnet-snapshot`, {
stdio: "pipe",
}).toString();
console.log(output);
}
// activate testnet
if (action.selectedIndex == 3) {
// get vnet details
const testNet = await virtual_test_net_1.default.getTestnet(testnet.selectedText);
// // overwrite RPC, Testnet UUID and Fork ID in .env
virtual_test_net_1.default.addToEnvFile("VIRTUAL_MAINNET_RPC", testNet?.admin_rpc ?? "");
virtual_test_net_1.default.addToEnvFile("TENDERLY_TESTNET_UUID", testNet?.vnet_id ?? "");
virtual_test_net_1.default.addToEnvFile("TENDERLY_FORK_ID", testNet?.network_id?.toString() ?? "1");
// overwrite Snapshot in .env
const output = (0, child_process_1.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 virtual_test_net_1.default.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_kit_1.terminal.yesOrNo().promise;
if (confirmDeploy?.valueOf()) {
console.log("\nDeploying default safes...");
const output = (0, child_process_1.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 virtual_test_net_1.default.addToEnvFile("ROLES_VERSION", "v2");
let output;
// show menu to select whitelisting options
const whitelistOptions = ["Whitelist all", "Whitelist one"];
const whitelistSelection = await terminal_kit_1.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_1.default.resolve("../access-control-safes-v2/src/roles");
const whitelistNames = permissionsFiles.map(file => path_1.default.relative(rolesDirectory, file));
const permissionSelection = await terminal_kit_1.terminal.singleColumnMenu(whitelistNames).promise;
const selectedFile = permissionsFiles.find(file => path_1.default.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_kit_1.terminal.processExit(0);
}
function executeWithLogs(command, options = {}) {
try {
// Merge default options with user provided options
const defaultOptions = {
encoding: "utf8",
stdio: "pipe",
maxBuffer: 1024 * 1024 * 10, // 10MB buffer
...options,
};
// Execute the command and capture output
const output = (0, child_process_1.execSync)(command, defaultOptions);
return {
success: true,
output: (0, file_manipulation_1.stripAnsi)(output.toString()),
error: null,
};
}
catch (error) {
// 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.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 virtual_test_net_1.default.addToEnvFile("ROLES_VERSION", "v2");
console.log(`create testnet: ${newTestnet}`);
const result = await virtual_test_net_1.default.createVirtualTestNet(newTestnet, chain);
// set env variables
await virtual_test_net_1.default.addToEnvFile("TENDERLY_FORK_ID", chain.toString());
// get snapshot
const outputSnapshot = (0, child_process_1.execSync)(`npm run save:vnet-snapshot`, {
stdio: "pipe",
}).toString();
console.log(outputSnapshot);
return {
testnetName: newTestnet,
result,
};
}
function getWhitelistsV2() {
const rolesDirectory = path_1.default.resolve("../access-control-safes-v2/src/roles");
try {
// Check if directory exists before trying to read it
if (fs_1.default.existsSync(rolesDirectory)) {
return (0, util_1.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 [];
}
}