UNPKG

tenderly-wizard-v6

Version:

A tool for managing virtual testnets using Tenderly

285 lines (284 loc) 13 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.setERC20TokenBalance = exports.encodeBytes32String = exports.getPreValidatedSignatures = exports.ExecutionOptions = exports.OperationType = exports.SALT = void 0; exports.createMultisendTx = createMultisendTx; exports.scopeTargetsV1 = scopeTargetsV1; exports.scopeTargetsV2 = scopeTargetsV2; exports.setGas = setGas; exports.findPermissionsFiles = findPermissionsFiles; exports.findWhitelistClasses = findWhitelistClasses; exports.checkRequiredEnvVariables = checkRequiredEnvVariables; exports.predictRolesModAddress = predictRolesModAddress; exports.predictSafeAddress = predictSafeAddress; exports.setUniformBlockNumber = setUniformBlockNumber; const ethers_1 = require("ethers"); const ethers_multisend_1 = require("ethers-multisend"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); // @ts-ignore const hardhat_1 = require("hardhat"); const ts_morph_1 = require("ts-morph"); const env_config_1 = __importDefault(require("../env-config")); const zodiac_1 = require("@gnosis-guild/zodiac"); exports.SALT = "0x0000000000000000000000000000000000000000000000000000000000000000"; var OperationType; (function (OperationType) { OperationType[OperationType["Call"] = 0] = "Call"; OperationType[OperationType["DelegateCall"] = 1] = "DelegateCall"; })(OperationType || (exports.OperationType = OperationType = {})); var ExecutionOptions; (function (ExecutionOptions) { ExecutionOptions[ExecutionOptions["None"] = 0] = "None"; ExecutionOptions[ExecutionOptions["Send"] = 1] = "Send"; ExecutionOptions[ExecutionOptions["DelegateCall"] = 2] = "DelegateCall"; ExecutionOptions[ExecutionOptions["Both"] = 3] = "Both"; })(ExecutionOptions || (exports.ExecutionOptions = ExecutionOptions = {})); /** * Encodes multiple transactions so we can then use a Safe with multisend for atomic transacting * @param {PreparedTransactionRequest[]} populatedTxs - Array of populated transactions * @param {string} multisendAddr - Address of the multisend contract * @returns {MetaTransaction} Encoded multi-transaction */ function createMultisendTx(populatedTxs, multisendAddr) { const safeTransactionData = populatedTxs.map(popTx => ({ to: popTx.to, value: popTx.value ? popTx.value.toString() : "0", data: popTx.data, })); return (0, ethers_multisend_1.encodeMulti)(safeTransactionData, multisendAddr); } /** * Generates pre-validated signatures for a single owner on a safe * @param {string} from - Address of the signer * @param {string} [initialString="0x"] - Initial string to prepend * @returns {string} Pre-validated signature string */ const getPreValidatedSignatures = (from, initialString = "0x") => { return `${initialString}000000000000000000000000${from.replace("0x", "")}000000000000000000000000000000000000000000000000000000000000000001`; }; exports.getPreValidatedSignatures = getPreValidatedSignatures; /** * Helper function to scope targets in roles contract v1 * @param {string[]} targetAddrs - Array of target addresses * @param {number} roleId - Role ID * @param {Contract} roles - Roles contract instance * @returns {Promise<PreparedTransactionRequest[]>} Array of populated transactions */ async function scopeTargetsV1(targetAddrs, roleId, roles) { const scopeTargetTxs = await Promise.all(targetAddrs.map(async (target) => { //Before granular function/parameter whitelisting can occur, you need to bring a target contract into 'scope' via scopeTarget const tx = await roles.scopeTarget.populateTransaction(hardhat_1.ethers.toBeHex(roleId, 32), target); return tx; })); return scopeTargetTxs; } /** * Helper function to scope targets in roles contract v2 * @param {string[]} targetAddrs - Array of target addresses * @param {`0x${string}`} roleId - Role ID * @param {Contract} roles - Roles contract instance * @returns {Promise<PreparedTransactionRequest[]>} Array of populated transactions */ async function scopeTargetsV2(targetAddrs, roleId, roles) { const scopeTargetTxs = await Promise.all(targetAddrs.map(async (target) => { const tx = await roles.scopeTarget.populateTransaction(hardhat_1.ethers.toBeHex(roleId, 32), target); return tx; })); return scopeTargetTxs; } /** * Encodes a string to bytes32 format * @param {string} text - String to encode * @returns {`0x${string}`} Bytes32 representation of the string */ exports.encodeBytes32String = hardhat_1.ethers.encodeBytes32String; /** * Sets ERC20 token balance for a single token and recipient * @param {string} token - Token address * @param {string} address - Recipient address * @param {BigNumberish} amount - Amount to set * @returns {Promise<void>} */ const setERC20TokenBalance = async (token, address, amount) => { const value = hardhat_1.ethers.hexlify(amount); await hardhat_1.network.provider.request({ method: "tenderly_setErc20Balance", params: [token, address, value.replace("0x0", "0x")], }); }; exports.setERC20TokenBalance = setERC20TokenBalance; /** * Sets gas balance for multiple addresses * @returns {Promise<void>} */ async function setGas() { const { VIRTUAL_MAINNET_RPC } = env_config_1.default; let caller; let manager; let dummyOwnerOne; let dummyOwnerTwo; let dummyOwnerThree; let security; [caller, manager, dummyOwnerOne, dummyOwnerTwo, dummyOwnerThree, security] = await hardhat_1.ethers.getSigners(); const provider = new ethers_1.JsonRpcProvider(VIRTUAL_MAINNET_RPC); await provider.send("tenderly_setBalance", [ caller.address, "0x8AC7230489E80000", ]); await provider.send("tenderly_setBalance", [ manager.address, "0x8AC7230489E80000", ]); await provider.send("tenderly_setBalance", [ security.address, "0x8AC7230489E80000", ]); await provider.send("tenderly_setBalance", [ dummyOwnerOne.address, "0x8AC7230489E80000", ]); await provider.send("tenderly_setBalance", [ dummyOwnerTwo.address, "0xDE0B6B3A7640000", ]); await provider.send("tenderly_setBalance", [ dummyOwnerThree.address, "0xDE0B6B3A7640000", ]); } /** * Finds all 'permissions.ts' files in a directory and its subdirectories * @param {string} dir - Directory to search * @returns {string[]} Array of file paths */ function findPermissionsFiles(whitelistDir) { if (!fs_1.default.existsSync(whitelistDir)) { throw new Error(`The directory ${whitelistDir} does not exist.`); } let results = []; // Get absolute path of whitelistDir const absoluteWhitelistDir = path_1.default.resolve(process.cwd(), whitelistDir); const files = fs_1.default.readdirSync(absoluteWhitelistDir); for (const file of files) { const filePath = path_1.default.join(absoluteWhitelistDir, file); const stat = fs_1.default.statSync(filePath); if (stat.isDirectory()) { // Recursively search subdirectories by calling findPermissionsFiles again results = results.concat(findPermissionsFiles(filePath)); } else if (file === "permissions.ts") { results.push(filePath); } } return results; } /** * Finds all classes that extend Whitelist in a directory * @param {string} whitelistDir - Directory to search * @returns {{ path: string, className: string }[]} Array of objects containing path and class name */ function findWhitelistClasses(whitelistDir) { const project = new ts_morph_1.Project(); const getAllTsFiles = (dir) => { let results = []; const list = fs_1.default.readdirSync(dir); list.forEach(file => { const filePath = path_1.default.join(dir, file); const stat = fs_1.default.statSync(filePath); if (stat && stat.isDirectory()) { results = results.concat(getAllTsFiles(filePath)); } else if (file.endsWith(".ts")) { results.push(filePath); } }); return results; }; getAllTsFiles(whitelistDir).forEach(file => { project.addSourceFileAtPath(file); }); const whitelistExtensions = []; project.getSourceFiles().forEach(sourceFile => { const classes = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassDeclaration); classes.forEach((classDeclaration) => { const heritage = classDeclaration.getHeritageClauses(); if (heritage.some(clause => clause.getTypeNodes().some(node => { const text = node.getText(); return (text.includes("Whitelist") || text.includes("AbstractPendleWhitelist")); }))) { const absolutePath = sourceFile.getFilePath(); whitelistExtensions.push({ path: absolutePath, className: classDeclaration.getName() ?? "", }); } }); }); // Filter out AccessControllerWhitelist classes let filteredExtensions = whitelistExtensions.filter(extension => !extension.className.includes("AccessControllerWhitelist")); whitelistExtensions.length = 0; whitelistExtensions.push(...filteredExtensions); return whitelistExtensions; } /** * Checks if required environment variables are present * @param {string[]} requiredVariables - Array of required variable names * @returns {boolean} True if all required variables are present, false otherwise */ function checkRequiredEnvVariables(requiredVariables) { const missingVariables = requiredVariables.filter(variable => !(variable in env_config_1.default)); if (missingVariables.length > 0) { console.log(`Missing required environment variables: ${missingVariables.join(", ")}`); return false; } console.log("All required environment variables are present."); return true; } async function predictRolesModAddress(signer, owner, avatar, target, rolesVersion) { const chainId = Number(await signer.provider.getNetwork().then((n) => n.chainId)); const encodedInitParams = ethers_1.AbiCoder.defaultAbiCoder().encode(["address", "address", "address"], [owner, avatar, target]); const rolesContract = rolesVersion == "v1" ? zodiac_1.KnownContracts.ROLES_V1 : zodiac_1.KnownContracts.ROLES_V2; const moduleSetupData = zodiac_1.ContractFactories[rolesContract] .createInterface() .encodeFunctionData("setUp", [encodedInitParams]); return (0, zodiac_1.calculateProxyAddress)(zodiac_1.ContractFactories[zodiac_1.KnownContracts.FACTORY].connect(zodiac_1.ContractAddresses[chainId][zodiac_1.KnownContracts.FACTORY], signer), zodiac_1.ContractAddresses[chainId][rolesContract], moduleSetupData, exports.SALT); } /** * Predicts the address of a Safe proxy that will be deployed through a Safe proxy factory * @param {Contract} safeProxyFactory - Instance of the Safe proxy factory contract * @param {string} safeMasterCopy - Address of the Safe master copy implementation * @param {string} data - Initialization data for the Safe proxy * @param {number} saltNonce - Nonce used as salt for address calculation * @returns {Promise<string>} The predicted address of the Safe proxy * @description Uses the proxy factory's calculateCreateProxyWithNonceAddress method to predict * the deterministic address where a Safe proxy will be deployed based on the initialization parameters */ async function predictSafeAddress(safeProxyFactory, safeMasterCopy, data, saltNonce) { return await safeProxyFactory.calculateCreateProxyWithNonceAddress(safeMasterCopy, data, saltNonce, { gasLimit: hardhat_1.ethers.hexlify(3000000), }); } /** * Sets the current block number to a target block number by increasing blocks * @param {number} targetBlock - The desired block number to reach * @returns {Promise<void>} A promise that resolves when blocks have been increased * @description This function is useful for testing scenarios that require being at a specific block number. * It calculates the difference between current and target block numbers and increases blocks accordingly. */ async function setUniformBlockNumber(targetBlock) { const currentBlock = await hardhat_1.network.provider.send("eth_blockNumber"); const currentBlockDecimal = parseInt(currentBlock, 16); const blocksToIncrease = targetBlock - currentBlockDecimal; console.log("targetBlock:", targetBlock); console.log("currentBlock:", currentBlockDecimal); console.log("blocksToIncrease:", blocksToIncrease); await hardhat_1.network.provider.request({ method: "evm_increaseBlocks", params: [hardhat_1.ethers.hexlify(blocksToIncrease)], }); }